一、基础复习
1.类和对象(I)总结 类的定义、self的用法
2.类的继承(II)、类的判断isinstance()、issubclass()、多重继承、类的组合
3.类和对象(III)总结 类的绑定,self,__dict__的妙用
二、类的继承
1.构造函数
这个函数是可以通过参数来进行个性化定制的。只要在定义类的同时定义一个构造函数,就可以自由发挥了。
构造函数有一个特殊的名称叫__init__(),,只需要在类中定义__init__方法,就可以在实例化对象的同时实现个性化定制。
例1:
>>> class C:
def __init__(self,x,y):
self.x=x #等号左边的x是绑定到实例化对象里面的x属性,等号右边的x是传进来的参数。
self.y=y
def add(self):
return self.x+self.y
def mul(self):
return self.x*self.y
>>>
>>> c=C(2,3)
>>> c.add()
5
>>> c.mul()
6
>>> c.__dict__
{'x': 2, 'y': 3}
>>> d=C(4,5)
>>> d.__dict__
{'x': 4, 'y': 5}
>>> d.add()
9
>>> d.mul()
20
>>>
2.重写
如果对于父类的某个属性或某个方法不满意的话,完全可以重新写一个同名的属性或方法对其进行覆盖。
例2:
>>> class D(C):
def __init__(self,x,y,z):
C.__init__(self,x,y)
self.z=z
def add(self):
return C.add(self)+self.z
def mul(self):
return C.mul(self)*self.z
>>> d=D(2,3,4)
>>> d.add()
9
>>> d.mul()
24
>>>
3.钻石继承
具体看例3
例3:
>>> class A:
def __init__(self):
print("哈喽,我是A")
>>> class B1(A):
def __init__(self):
A.__init__(self)
print("哈喽,我是B1")
>>> class B2(A):
def __init__(self):
A.__init__(self)
print("哈喽,我是B2")
>>> class C(B1,B2):
def __init__(self):
B1.__init__(self)
B2.__init__(self)
print("哈喽,我是C")
>>> c=C()
哈喽,我是A # A重复初始化了两次,因为C继承自B1,B2,而B1,B2继承自A,因此A重复了两次
哈喽,我是B1
哈喽,我是A
哈喽,我是B2
哈喽,我是C
为了解决钻石继承的问题,引入了super()函数。
4.super()函数
super()函数能够在父类中搜索指定的方法,并自动绑定好self参数。
使用super()函数去查找父类的方法,它就会自动按照MRO顺序去搜索父类的相关方法,并且自动避免重复调用的问题。
MRO详解
例4:
>>> class A:
def __init__(self):
print("哈喽,我是A")
>>> class B1(A):
def __init__(self):
super().__init__()
print("哈喽,我是B1")
>>> class B2(A):
def __init__(self):
super().__init__()
print("哈喽,我是B2")
>>> class C(B1,B2):
def __init__(self):
B1.__init__(self)
B2.__init__(self)
print("哈喽,我是C")
>>> C.mro()
[<class '__main__.C'>, <class '__main__.B1'>, <class '__main__.B2'>, <class '__main__.A'>, <class 'object'>]
>>>
>>> B1.mro()
[<class '__main__.B1'>, <class '__main__.A'>, <class 'object'>]
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B1'>, <class '__main__.B2'>, <class '__main__.A'>, <class 'object'>)
>>>
课后题:
1.Python 中的构造函数(init()),到底是函数还是方法?
答:方法。
解析:在面向对象的编程语言中,我们习惯将对象的初始化方法称之为构造函数或构造器。但我们也知道,Python 中函数和方法的区别是,是否通过第一个参数绑定了相关对象,从这一点上来看,严谨的说 init() 应该称之为“构造方法”。
2.如果按照下面代码的方式去定义一个类,其实是存在一定风险的,你能指出问题所在,并给予修改的意见吗?
>>> class C:
... x = []
... def add_x(self, x):
... self.x.append(x)
答:这里说的 “风险”,其实指的是将 x 定义为类属性这个事儿。
如果将代码改为下面这样,整体上会更好一些:
>>> class C:
... def __init__(self):
... self.x = []
... def add_x(self, x):
... self.x.append(x)
...
>>> c = C()
>>> d = C()
>>> c.add_x(250)
>>> d.add_x(520)
>>> c.x
[250]
>>> d.x
[520]
解析:类讲究共享,对象追求独立
3.请问下面代码中,为什么 class C(A, B) 就会报错,而 class C(B, A) 则不会报错呢?
>>> class A:
... pass
...
>>> class B(A):
... pass
...
>>> class C(A, B):
... pass
...
Traceback (most recent call last):
File "<pyshell#49>", line 1, in <module>
class C(A, B):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases A, B
>>>
>>> # 改成下面这样就不报错
>>> class C(B, A):
... pass
...
答:如果写成 class C(A, B) 则会造成冲突,因为根据继承优先级逻辑,C 是先去找 A 再去找 B,此时 A 的优先级比 B 高,但 B 又是继承自 A 的,所以从这个角度来看,B 的优先级又比 A 要高了。
4.请问下面代码会打印什么呢?
>>> class A:
... x = 250
...
>>> class B(A):
... pass
...
>>> class C(B):
... pass
...
>>> class D:
... x = 520
...
>>> class E(C, D):
... pass
...
>>> E.x
>>> # 请问这里会打印什么内容?
答:250
解析:根据代码的继承关系,梳理如下图~
按照 MRO 规则,继承顺序应该是 E -> C -> B -> A -> D
>>> E.mro()
[<class '__main__.E'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.D'>, <class 'object'>]
所以,由于只有类 A 和 D 有定义 x,且 A 的优先级比 D 要高,所以拿到的结果是 A.x。
对 MRO 规则不清晰,建议务必要认真看一下这篇文章MRO详解
5.请问下面代码会打印什么呢?
>>> class A:
... x = 250
... def add_x(self):
... self.x += 1
...
>>> class B(A):
... x = 300
... def add_x(self):
... A.add_x(self)
...
>>> class C(A):
... x = 500
... def add_x(self):
... A.add_x(self)
...
>>> class D(B, C):
... pass
...
>>> d = D()
>>> d.add_x()
>>> d.x
>>> # 请问这里会打印什么内容?
答:
>>> d.x
301
解析:
- 先确定 x 的值,根据继承优先级顺序关系(MRO),由于类 D 没有定义 x,所以使用的是类 B 的 x 值;
- 然后 add_x() 方法调用的是谁的呢?同样的道理,根据 MRO 顺序来查找,D 中没有,那么就到 B 中去找,B 中有 add_x() 的定义,不过它调用的是 A.add_x(),所以,最终执行的是 A 里面的 add_x() 方法。
6.上面代码中的 A.add_x(self),如果替换成 super() 函数,应该怎么写?
答:应该是 super().add_x()。
解析:如果使用了 super(),就不必自己传递 self 参数
7.请问下面代码会打印什么呢?
>>> class A:
... def ping(self):
... print("A ping~")
...
>>> class B(A):
... def pong(self):
... print("B pong~")
...
>>> class C(A):
... def pong(self):
... print("C pong~")
...
>>> class D(B, C):
... def pingpong(self):
... self.ping()
... self.pong()
...
>>> d = D()
>>> d.pingpong()
>>> # 请问这里会打印什么内容?
答:
>>> d.pingpong()
A ping~
B pong~
解析:
- 由于类 B、C、D 均没有实现 ping() 方法,所以 self.ping() 根据继承关系,会去调用类 A 的 ping() 方法。
- 由于类 B、C 都有实现 pong() 方法,根据 MRO 顺序类 B 的覆盖优先级比类 C 高,所以 self.pong() 会去调用类 B 的 pong() 方法。
题目来自小甲鱼类和对象(III)