0%

Python学习笔记-对象和类

使用class定义类

Python中使用class关键字来定义类,下面定义一个Person类

1
2
3
4
5
6
7
8
class Person():
def __init__(self, name):
self.name = name


if __name__ == '__main__':
hunter = Person('Elmer Fudd')
print(hunter.name)

其中 hunter = Person(‘Elmer Fudd’) 这一行代码执行的操作有:

    1. 查看Person类的定义
    1. 在内存中创建一个新的对象
    1. 调用对象的__init__方法,将这个新创建的对象作为self传入,并将另一个参数(‘Elmer Fudd’)作为name传入
    1. 将name的值存入对象
    1. 返回这个新的对象
    1. 将名字hunter与这个对象关联
      在类的定义中,__init__方法并不是必须的。只有当需要区分该类创建的不同对象时,才需要指定__init__方法。

继承

创建两个类,Car类和Yugo类,Yugo类继承自Car类,同时,Yugo类覆盖了Car类的exclaim方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Car():
def exclaim(self):
print("I'm a Car!")


class Yugo(Car):
def exclaim(self):
print("I'm a Yugo! Much like a Car, but more Yugo-ish.")


if __name__ == '__main__':
give_me_a_car = Car()
give_me_a_yugo = Yugo()
give_me_a_car.exclaim()
give_me_a_yugo.exclaim()

程序运行结果:

1
2
I'm a Car!
I'm a Yugo! Much like a Car, but more Yugo-ish.

在子类中,可以覆盖任何父类的方法,包括__init__()。下面使用代码来说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person():
def __init__(self, name):
self.name = name


class MDPerson(Person):
def __init__(self, name):
self.name = "Doctor " + name


class JDPerson(Person):
def __init__(self, name):
self.name = name + ", Esquire"


if __name__ == '__main__':
person = Person('Fudd')
doctor = MDPerson('Fudd')
lawyer = JDPerson('Fudd')
print(person.name)
print(doctor.name)
print(lawyer.name)

程序的运行结果:

1
2
3
Fudd
Doctor Fudd
Fudd, Esquire

子类可以添加父类中没有的方法。新增的方法子类对象可以调用,父类对象无法调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Car():
def exclaim(self):
print("I'm a Car!")


class Yugo(Car):
def exclaim(self):
print("I'm a Yugo! Much like a Car, but more Yugo-ish.")

def need_a_push(self):
print("A little help here?")


if __name__ == '__main__':
give_me_a_car = Car()
give_me_a_yugo = Yugo()
give_me_a_yugo.need_a_push()
give_me_a_car.need_a_push()

程序的运行结果:

1
2
3
4
5
A little help here?
Traceback (most recent call last):
File "/Users/kris/PycharmProjects/pythons_demo/Python语言及其应用/对象和类/创建类.py", line 41, in <module>
give_me_a_car.need_a_push()
AttributeError: 'Car' object has no attribute 'need_a_push'

使用super()从父类获得帮助。子类中想要调用父类的方法需要使用super()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person():
def __init__(self, name):
self.name = name


class EmailPerson(Person):
def __init__(self, name, email):
super().__init__(name)
self.email = email


if __name__ == '__main__':
bob = EmailPerson('Bob Frapples', 'bob@frapples.com')
print(bob.name)
print(bob.email)

在子类中定义__init__()方法时,父类的__init__()方法会被覆盖。因此在子类中,父类的初始化方法并不会被自动调用,我们必须显式调用。这样有什么好处呢?这样处理的好处在于子类在实例化的时候会经过父类,如果父类中对name属性进行了业务处理,子类也会体现出来,否则父类的name将来需要修改还需要修改一遍子类的实现。
程序运行结果:

1
2
Bob Frapples
bob@frapples.com

Python使用self参数来找到正确对象所包含的特性和方法。通过下面的例子,说明Python在调用对象方法背后实际做的工作。

1
2
3
4
5
6
7
8
9
class Car():
def exclaim(self):
print("I'm a Car!")


if __name__ == '__main__':
car = Car()
car.exclaim()
Car.exclaim(car)

Python在背后所做的事情:

    1. 查找car对象所属的类(Car)
    1. 把car对象作为self参数传给Car类所包含的exclaim()方法
      上面程序运行的结果:
      1
      2
      I'm a Car!
      I'm a Car!

      使用属性对特性进行访问和设置

      有一些面向对象的语言支持私有特性。这些特性无法从对象外部直接访问,我们需要编写getter和setter方法对这些私有特性进行读写操作。Python不需要getter和setter方法,因为Python里所有特性都是公开的。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      class Duck():
      def __init__(self, input_name):
      self.hidden_name = input_name

      def get_name(self):
      print('inside the getter')
      return self.hidden_name

      def set_name(self, input_name):
      print('inside the setter')
      self.hidden_name = input_name

      name = property(get_name, set_name)


      if __name__ == '__main__':
      fowl = Duck('Howard')
      print(fowl.name)
      fowl.get_name()
      fowl.name = 'Daffy'
      print(fowl.name)
      fowl.set_name('ZhangSan')
      print(fowl.name)
      这两个新方法在最后一行之前都与普通的getter和setter方法没有区别,而最后一行则把这两个方法定义为了name属性。property()的第一个参数是getter方法,第二个参数是setter方法。现在当你尝试访问Duck类对象的name属性时,get_name()会被自动调用,当然也可以显式调用get_name()方法,它就像普通的getter方法一样;当对name属性进行赋值的时候,set_name()方法会被调用,也可以显式调用set_name()方法。
      运行的结果如下:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      inside the getter
      Howard
      inside the getter
      inside the setter
      inside the getter
      Daffy
      inside the setter
      inside the getter
      ZhangSan

使用属性修饰符定义属性,@property 修饰符用于指示getter方法,@name.setter 用于指示setter方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Duck():
def __init__(self, input_name):
self.hidden_name = input_name

@property
def name(self):
print('inside the getter')
return self.hidden_name

@name.setter
def name(self, input_name):
print('inside the setter')
self.hidden_name = input_name

if __name__ == '__main__':
fowl = Duck('Howard')
print(fowl.name)
# fowl.get_name()
fowl.name = 'Daffy'
print(fowl.name)
# fowl.set_name('ZhangSan')
print(fowl.name)
print(fowl.hidden_name)
fowl.hidden_name = "lisi"
print(fowl.hidden_name)

仍然可以像之前一样访问属性一样访问name,但是这里没有了显式的get_name()和set_name()方法,所以我注掉了显式调用的代码,不然会报错。如果有人猜到在类的内部用的变量名是hidden_name,他仍然可以直接通过fowl.hidden_name进行读写操作。运行结果如下:

1
2
3
4
5
6
7
8
9
inside the getter
Howard
inside the setter
inside the getter
Daffy
inside the getter
Daffy
Daffy
lisi

使用名称重整保护私有变量

前面的Duck例子中,为了隐藏内部的变量,我们曾将其命名为hidden_name。其实,Python对那些需要刻意隐藏在类内部的特性有自己的命名规范:由连续的两个下划线开头(__)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Duck():
def __init__(self, input_name):
self.__name = input_name

@property
def name(self):
print('inside the getter')
return self.__name

@name.setter
def name(self, input_name):
print('inside the setter')
self.__name = input_name


if __name__ == '__main__':

fowl = Duck('Howard')
print(fowl.name)
fowl.name = 'Donald'
print(fowl.name)
# print(fowl.__name)
fowl.__name = "zhangsan"
print(fowl.name)
print(fowl._Duck__name)

这种命名规范本质上并没有把变量变成私有,但是Python确实将它的名字重整了,让外部的代码无法使用。最后一行打印可以成功绕过getter方法,但是这种命名重整能在一定程度上避免我们无意或有意的对变量进行直接访问。
运行结果:

1
2
3
4
5
6
7
8
inside the getter
Howard
inside the setter
inside the getter
Donald
inside the getter
Donald
Donald

方法的类型

  • 1.实例方法
  • 2.类方法(@classmethod)
  • 3.静态方法(@staticmethod)

有些数据(变量) 和函数(方法)是类本身的一部分,还有一些是由类创建的实例的一部分。在类的定义中,以self作为第一个参数的方法都是实例方法。他们在创建自定义类时最常见。实例方法的首个参数是self,当它被调用时,Python会把调用该方法的对象作为self参数传入。与之相对,类方法会作用于整个类,对类作出的任何改变会对它的所有实例对象产生影响。在类定义内部,用前缀修饰符@classmethod指定的方法都是类方法。与实例方法类似,类方法的第一个参数是类本身。在Python中,这个参数常被写作cls,因为全程class是保留字,在这里我们无法使用。下面的例子中,我们为A定义一个类方法来记录一共多少个类A的对象被创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A():
count = 0
def __init__(self):
A.count += 1

def exclaim(self):
print("I'm an A!")
@classmethod
def kids(cls):
print("A has", cls.count, "little objects.")


if __name__ == '__main__':

easy_a = A()
breezy_a = A()
wheezy_a = A()
A.kids()
print(A.count)

上面的代码中,在kids()方法中,我们使用的是cls.count,它与A.count的作用一样。
运行结果:

1
2
A has 3 little objects.
3

类定义中的方法还存在第三种类型,它既不会影响类,也不会影响对象。他们出现在类的定义中仅仅是为了方便,这种类型的方法被称为静态方法,使用@staticmethod修饰,它既不需要self参数也不需要class参数。下面的例子中的静态方法是一则CoyoteWeapon的广告:

1
2
3
4
5
6
7
8
9
class CoyoteWeapon():
@staticmethod
def commercial():
print('This CoyoteWeapon has been brought to you by Acme')


if __name__ == '__main__':

CoyoteWeapon.commercial()

在这个例子中,我们甚至都不用创建任何CoyoteWeapon类的对象就可以调用这个方法,句法优雅不失风格!

特殊方法

Python中有些特殊的方法,这些特殊方法的名称以双下划线(__)开头和结束,有时也被称作__魔术方法__。
下面列出常见的魔术方法:

  • 1.和比较相关的魔术方法
  • 2.和数学相关的魔术方法
  • 3.其他种类的魔术方法

下面定义魔术方法__eq__()__str__()__repr__(),看看运行的结果如何。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Word():

def __init__(self, text):
self.text = text

def __eq__(self, other):
return self.text.lower() == other.text.lower()

def __str__(self):
return self.text

def __repr__(self):
return 'Word("'+self.text+'")'



if __name__ == '__main__':

first = Word('ha')
print(first)
print(first == Word('Ha'))

定义__eq__()魔术方法方便同一类型的对象进行比较。定义 __str__()魔术方法方便print方法打印对象的相关信息,定义 __repr__() 魔术方法方便控制台输出对象的相关信息。
程序的运行结果如下:

1
2
ha
True