本文共 4726 字,大约阅读时间需要 15 分钟。
面向对象三大特性
封装
继承
多态
多个不同的类具有共同的方法f,各个类调用方法f,返回值不同。把方法f提取出来,封装为一个接口g。不同类的实例作为参数,传入接口g,得到不同返回值。
classDog:deftalk(self):print('汪汪汪...')classCat:deftalk(self):print('喵喵喵...')classBird:deftalk(self):print('叽叽喳喳...')defprint_talk(obj):
obj.talk()if __name__ == '__main__':
d=Dog()
c=Cat()
b=Bird()
print_talk(d)#汪汪汪...
print_talk(c) #喵喵喵....
print_talk(b) #叽叽喳喳...
View Code
Pyhon不支持Java和C#这一类强类型语言中多态的写法,但是原生多态,其Python崇尚“鸭子类型”。
classFather:pass
classS1(Father):defshow(self):print('in S1 func show')classS2(Father):defshow(self):print('in S2 func show')#由于在Java或C#中定义函数参数时,必须指定参数的类型#为了让Func函数既可以执行S1对象的show方法,又可以执行S2对象的show方法,所以,定义了一个S1和S2类的父类#而实际传入的参数是:S1对象和S2对象
defFunc(F obj):"""Func函数需要接收一个F1类型或者F1子类的类型"""obj.show()
s1_obj=S1()
Func(s1_obj)#在Func函数中传入S1类的对象 s1_obj,执行 S1 的show方法,结果:S1.show
s2_obj=S2()
Func(s2_obj)#在Func函数中传入Ss类的对象 ss_obj,执行 Ss 的show方法,结果:S2.show
Python伪代码实现Java或C#的多态
super
在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用 super 来实现,比如:
classAnimal(object):def __init__(self, name):
self.name=namedefeat(self):print('in Animal, eat func')classDog(Animal):defeat(self):
super(Dog, self).eat()print('in Dog, eat func')if __name__ == '__main__':
d= Dog('大黄')
d.eat()'''in Animal, eat func
in Dog, eat func'''
子类重写父类方法
看了上面的使用,你可能会觉得 super 的使用很简单,无非就是获取了父类,并调用父类的方法。其实,在上面的情况下,super 获得的类刚好是父类,但在其他情况就不一定了,super 其实和父类没有实质性的关联。
classBase(object):def __init__(self):print('enter Base')print('leave Base')classA(Base):def __init__(self):print('enter A')
super().__init__()print('leave A')classB(Base):def __init__(self):print('enter B')
super().__init__()print('leave B')classC(A, B):def __init__(self):print('enter C')
super().__init__()print('leave C')if __name__ == '__main__':
c=C()print(C.mro())'''enter C
enter A
enter B
enter Base
leave Base
leave B
leave A
leave C
[, , , , ]'''
super 多重继承
如果你认为 super 代表『调用父类的方法』,那你很可能会疑惑为什么 enter A 的下一句不是 enter Base 而是 enter B。原因是,super 和父类没有实质性的关联,现在让我们搞清 super 是怎么运作的。
事实上,对于你定义的每一个类,Python 会计算出一个方法解析顺序(Method Resolution Order, MRO)列表,它代表了类继承的顺序,我们可以使用mro()获得某个类的 MRO 列表。
那这个 MRO 列表的顺序是怎么定的呢,它是通过一个 C3 线性化算法来实现的,这里我们就不去深究这个算法了,感兴趣的读者可以自己去了解一下,总的来说,一个类的 MRO 列表就是合并所有父类的 MRO 列表,并遵循以下三条原则:
子类永远在父类前面
如果有多个父类,会根据它们在列表中的顺序被检查
如果对下一个类存在两个合法的选择,选择第一个父类
super 的工作原理如下:
defsuper(cls, inst):
mro= inst.__class__.mro()return mro[mro.index(cls) + 1]
其中,cls 代表类,inst 代表实例,上面的代码做了两件事:
获取 inst 的 MRO 列表
查找 cls 在当前 MRO 列表中的 index, 并返回它的下一个类,即 mro[index + 1]
当你使用 super(cls, inst) 时,Python 会在 inst 的 MRO 列表上搜索 cls 的下一个类。
现在,让我们回到前面的例子。
首先看类 C 的 __init__ 方法:
这里的 self 是当前 C 的实例,self.__class__.mro() 结果是:
可以看到,C 的下一个类是 A,于是,跳到了 A 的 __init__,这时会打印出 enter A,并执行下面一行代码:
注意,这里的 self 也是当前 C 的实例,MRO 列表跟上面是一样的,搜索 A 在 MRO 中的下一个类,发现是 B,于是,跳到了 B 的 __init__,这时会打印出 enter B,而不是 enter Base。
整个过程还是比较清晰的,关键是要理解 super 的工作方式,而不是想当然地认为 super 调用了父类的方法。
5.小结
事实上,super 和父类没有实质性的关联。
super(cls, inst) 获得的是 cls 在 inst 的 MRO 列表中的下一个类。
C3算法
在python3中,所有的类都是新式类,继承顺序遵循广度优先的C3算法.
C3算法的规则如下:
每一个类的继承顺序都是从父类向子类看
形成一个指向关系的顺序[当前类]+[父类的继承顺序]
如果一个类出现在从左到右的第一个顺序上并且没有出现在后面顺序中或者出现在后面的顺序中但是仍然是第一顺序,那么就把这个类提取出来
例如下图:
由上图可知:B类,C类和D类是单继承,所以很容易看出L(B),L(C)和L(D)
L(B) = [BFGO]
L(C) = [CEO] (注:python3中所有类都是新式类,默认继承object类)
L[D] = [DGO]
然后分析多继承的A类的继承顺序:
L(A) = L(B)+L(C)+L(D)
A =[BFGO] + [CEO] + [DGO]
AB = [FGO] + [CEO] + [DGO]
ABF =[GO] + [CEO] + [DGO]
ABFC = [GO] + [EO] + [DGO]
ABFCE = [GO] + [O] + [DGO]
ABFCED = [GO] + [O] + [GO]
ABFCEDG = [O] + [O] + [O]
L(A) = [ABFCEDGO]
结果A类的继承顺序就为A,B,F,C,E,D,G,object.
也可以通过类的__mro__属性来查看当前类的继承顺序
super内置函数的寻找顺序也与此顺序相同
#C3 定义引用开始
C3 算法:MRO是一个有序列表L,在类被创建时就计算出来。
L(Child(Base1,Base2)) = [ Child + merge( L(Base1) , L(Base2) , Base1Base2 )]
L(object) = [ object ]
L的性质:结果为列表,列表中至少有一个元素即类自己。
+ : 添加到列表的末尾,即 [ A + B ] = [ A,B ]
merge: ① 如果列表空则结束,非空 读merge中第一个列表的表头,
② 查看该表头是否在 merge中所有列表的表尾中。
②-->③ 不在,则 放入 最终的L中,并从merge中的所有列表中删除,然后 回到①中
②-->④ 在,查看 当前列表是否是merge中的最后一个列表
④-->⑤ 不是 ,跳过当前列表,读merge中下一个列表的表头,然后 回到 ②中
④-->⑥ 是,异常。类定义失败。
表头: 列表的第一个元素 (列表:ABC,那么表头就是A,B和C就是表尾)
表尾: 列表中表头以外的元素集合(可以为空)
merge 简单的说即寻找合法表头(也就是不在表尾中的表头),如果所有表中都未找到合法表头则异常。
#C3定义引用结束
例如:
L(D) = L(D(O))
= D + merge(L(O))
= D + O
= [D,O]
L(B) = L(B(D,E))
= B + merge(L(D) , L(E))
= B + merge(DO , EO) # 第一个列表DO的表头D,其他列表比如EO的表尾都不含有D,所以可以将D提出来,即D是合法表头
= B + D + merge(O , EO) #从第一个开始表头是O,但是后面的列表EO的表尾中含有O所以O是不合法的,所以跳到下一个列表EO
= B + D + E + merge(O , O)
= [B,D,E,O]
同理:
L(C) = [C,E,F,O]
L(A(B,C)) = A + merge(L(B),L(C),BC)
= A + merge(BDEO,CEFO,BC)#B是合法表头
= A + B + merge(DEO,CEFO,C)#D是合法表头
= A + B + D + merge(EO,CEFO,C)#E不是合法表头,跳到下一个列表CEFO,此时C是合法表头
= A + B + D + C + merge(EO,EFO)#由于第三个列表中的C被删除,为空,所以不存在第三个表,只剩下两个表;此时E是合法表头
= A + B + D + C + E + merge(O,FO)#O不是合法表头,跳到下一个列表FO,F是合法表头,
= A + B + D + C + E + F + merge(O,O)#O是合法表头
= A + B + D + C + E + F + O
= [A,B,D,C,E,F,O]
获取C3的数组列表,可以梳理清楚子类执行过程中向上执行的顺序
转载地址:http://boqms.baihongyu.com/