Last Updated: 2023-11-07 11:24:34 Tuesday
-- TOC --
貌似有人说过,OOP编程是区分程序员水平的一个标志。我个人认为,熟练掌握OOP编程思想和技巧,是能够驾驭更加庞大代码量的前提,也是能够更好地对问题进行抽象的基础。
继承就是继承,将父类的变量和方法都变成自己的。多重继承就是有多个父类。这种结构很强大,用好了可以减少很多重复代码,但也非常容易导致错误。
示例:
class A:
def __init__(self):
print("Enter A")
print("Leave A")
class B(A):
def __init__(self):
print("Enter B")
A.__init__(self)
print("Leave B")
class C(A):
def __init__(self):
print("Enter C")
A.__init__(self)
print("Leave C")
class D(A):
def __init__(self):
print("Enter D")
A.__init__(self)
print("Leave D")
class E(B, C, D):
def __init__(self):
print("Enter E")
B.__init__(self)
C.__init__(self)
D.__init__(self)
print("Leave E")
E()
A是所有类型的父类,BCD都继承自A,E继承BCD。他们都重写了__init__方法,而且都直接调用父类的__init__函数,执行效果如下:
D:\py>python super.py
Enter E
Enter B
Enter A
Leave A
Leave B
Enter C
Enter A
Leave A
Leave C
Enter D
Enter A
Leave A
Leave D
Leave E
问题很明显,A的__init__函数被执行了多次!
解决问题的方法,就是调用super()
接口:
class A:
def __init__(self):
print("Enter A")
print("Leave A")
class B(A):
def __init__(self):
print("Enter B")
super().__init__()
print("Leave B")
class C(A):
def __init__(self):
print("Enter C")
super().__init__()
print("Leave C")
class D(A):
def __init__(self):
print("Enter D")
super().__init__()
print("Leave D")
class E(B, C, D):
def __init__(self):
print("Enter E")
super().__init__()
print("Leave E")
E()
继承关系不变,BCDE的__init__函数都采用super()函数来调用父类的__init__。执行效果如下:
D:\py>python super.py
Enter E
Enter B
Enter C
Enter D
Enter A
Leave A
Leave D
Leave C
Leave B
Leave E
E的初始化,不再重复调用A了!
使用super时,可以保证公共的父类接口仅被执行一次,至于执行的顺序,是按照MRO(Method Resolution Order)方法解析顺序进行的。简单地说,super会按照mro顺序查找出一个method来执行。
在Python中,super接口的调用不再需要参数,需要参数的是Python2的写法。
MRO,Method Resolution Order,应该是一个比较通用的概念,具体是指在有继承关系,特别是多重继承关系的OOP代码中,子类的对象在调用继承下来的函数时,要通过MRO的顺序,去定位具体的调用位置。
class aa:
def __init__(self):
print('in aa')
def fun(self):
print('in aa fun')
class bb:
def __init__(self):
print('in bb')
def fun(self):
print('in bb fun')
class cc(aa,bb):
pass
c = cc()
c.fun()
print(cc.mro())
cc继承aa和bb,因此cc的__init__和fun函数,都需要MRO来决策具体的调用位置,因为这两个函数,在aa和bb中都存在。这段代码的运行效果如下:
in aa
in aa fun
[<class '__main__.cc'>, <class '__main__.aa'>, <class '__main__.bb'>, <class 'object'>]
按照MRO的顺序,cc的两次调用,都定位在aa内。
MRO是把类的继承关系线性化的一个过程,而线性化的方式决定了程序运行过程中具体会调用哪个方法。
Python曾经至少有三种不同的MRO:
cc.mro()
输出类cc的MRO顺序list!
MRO顺序,其实就是代码中的书写顺序,aa写在bb前面,MRO中,aa就在bb前面!
与MRO密切相关的,是一个我们常见的super函数。
MRO满足两个性质:(1)本地优先;(2)单调性;
本地优先:指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。
单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。
阅读代码的过程中,看到另一种调用super按照MRO查找调用点的case:
class aa:
def __init__(self):
print('in aa')
def fun(self):
print('in aa fun')
def one(self):
super().one()
print('one in aa')
class bb:
def __init__(self):
print('in bb')
def fun(self):
print('in bb fun')
def one(self):
print('one in bb')
class cc(aa,bb):
pass
c = cc()
c.one()
print(cc.mro())
print(aa.mro())
print(bb.mro())
执行c.one(),按照mro的顺序,会调用aa.one,但是aa.one调用了super,aa本身并没有父类!这个super调到哪里去了?执行效果:
$ python3 test_class.py
in aa
one in bb
one in aa
[<class '__main__.cc'>, <class '__main__.aa'>, <class '__main__.bb'>, <class 'object'>]
[<class '__main__.aa'>, <class 'object'>]
[<class '__main__.bb'>, <class 'object'>]
显然,aa.one中的super,调用的是bb.one。还是用mro来解释,这条查找链是从cc开始的,在aa.one中执行super,按照查找连,自然会找到bb.one来执行。
__subclasses__()
获取所有子类$ cat t1.py
class A():
...
class B(A):
...
class C(A):
...
for cls in A.__subclasses__():
print(cls, cls.__name__)
$ python3 t1.py
<class '__main__.B'> B
<class '__main__.C'> C
我一直疑惑的unittest模块,它是如何找到unittest.TestCase的子类的?应该就是这个方法。
__bases__
获取父类$ cat t2.py
class A():
...
class B(A):
...
class C(A):
...
class D(B,C):
...
print(B.__bases__)
print(C.__bases__)
for cls in D.__bases__:
print(cls.__name__)
$ python3 t2.py
(<class '__main__.A'>,)
(<class '__main__.A'>,)
B
C
issubclass
,判断某个类是否是另一个类的子类;
isinstance
,判断某个对象是否是属于某个类的实例;
$ cat t3.py
class A():
...
class B(A):
...
class C(A):
...
class D(B,C):
...
print(issubclass(B,A))
print(issubclass(D,A))
b1 = B()
d1 = D()
print(isinstance(b1,D))
print(isinstance(d1,D))
$ python3 t3.py
True
True
False
True
本文链接:https://cs.pynote.net/sf/python/202204022/
-- EOF --
-- MORE --