资料参考
1 多继承中的问题
https://blog.csdn.net/zsh142537/article/details/82685721
2 C3算法
https://blog.csdn.net/fmblzf/article/details/52512145
3 mro查找方法
https://www.bilibili.com/video/av50691919?from=search&seid=1714314693359442856
文章目录
1.单继承
1.1 方法重写(覆盖)
- 在B类中有一个和父类A同名同参数的方法。
- 此时在B类里或者B的实例对象调用的fun方法是B里定义的fun。
实例.fun()
类名.fun(实例对象)
class A(object):
def fun(self):
print("A的fun方法")
class B(A):
def fun(self):
print("B的fun方法")
# 重写后直接调用的是父类方法
#通过对象调用方法
b=B()
b.fun()
#通过类调用方法
B.fun(b)
调用的是子类重写后的方法
B的fun方法
B的fun方法
1.2 在类中调用父类的方法
- 有三种调用方法
(1)父类名.函数名(参数)
(2)super().函数名(参数)
(3)super(本类的类名,self).函数名(参数) - 在类内部调用父类里被重写的fun方法
class A(object):
def fun(self):
print("A的fun方法")
class B(A):
def fun(self):
print("B的fun方法")
def get_A_fun(self):
A.fun(self)
class C(A):
def fun(self):
print("C的fun方法")
def get_A_fun(self):
super(C, self).fun()
class D(A):
def fun(self):
print("D的fun方法")
def get_A_fun(self):
super().fun()
# 子类内部访问父类方法
b=B()
b.fun()
b.get_A_fun()
c=C()
c.get_A_fun()
d=D()
d.get_A_fun()
访问的是父类内部的方法
A的fun方法
A的fun方法
A的fun方法
1.3 单继承中的构造方法问题
- 构造方法是一种特殊的方法,他和其他方法一样,如果父类构造方法被子类重写了,创建子类对象时就会自动调用子类构造方法。没有被重写,创建子类对象时会调用父类的构造方法。
- 创建普通对象调用构造方法时会开辟内存空间。在子类里可以显式调用父类的构造方法,但是不会开辟多余的内存空间,只会简单地执行__init__里面地语句。
class A(object):
def __init__(self):
print("父类的构造函数被调用")
class B(A):
pass
class C(A):
def __init__(self):
print("子类的构造函数被调用")
def fun(self):
A.__init__(self)
A()
print("---")
B()
print("---")
C().fun()
执行结果
父类的构造函数被调用
---
父类的构造函数被调用
---
子类的构造函数被调用
父类的构造函数被调用
2 多继承中的问题
多继承中最简单也是最经典的继承模式,钻石继承。
2.1 用类名直接调用父类的构造方法
用类名调用会发现有的方法被执行了多次。
class Parent(object):
def __init__(self, name):
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age):
Parent.__init__(self, name)
self.age = age
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender):
Parent.__init__(self, name)
self.gender = gender
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
Son1.__init__(self, name, age) # 单独调用父类的初始化方法
Son2.__init__(self, name, gender)
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
Grandson开始-Son1开始-Parent开始-Parent结束-Son1结束-Son2开始-Parent开始-Parent结束-Son2结束-Grandson结束。
parent的init结束被调用
Son1的init结束被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用
2.2 用super调用父类的构造方法
- 构造方法被执行了多次,显然是不合理的,用super().__init__方法可以确保每个构造方法只执行一遍。
- 这个执行顺序是根据就是mro「方法解析顺序」(Method Resolution Order).在2.7中它采用的是深度优先遍历,在2.7以上,采用的就是C3算法(稍后介绍)确定的。
class Parent(object):
def __init__(self, name, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
self.age = age
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
self.gender = gender
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
# 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
# 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
super().__init__(name, age, gender)
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print(Grandson.__mro__)
结果是
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
- 此处根据mro序列学习super的调用父类构造方法的原理。
通过两种方法可以查看mro序列
print(Grandson.mro)
print(Grandson.mro())
[<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>]
(1)在Grandson里__init__里,查看mro序列,super调用Grandson类后面的类Son1类的构造方法.
(2)Son1类里__init__里,查看mro序列,super调用Son1类后面的类Son2类的构造方法.
(3)Son2里的__init__里,super调用Son2类后面的类Parent类的构造方法.
2.3 用super传入参数调用父类的构造方法
- super(类名1,self).__init__方法,在自己的mro序列里找到类名1后面的那个类的构造方法。
- super(自己的类名,self)与super()效果相同。
2.4 三种调用的比较
- 类名直接调用,可能会调用多次
- super()在mro序列里找到当前类名后一个类调用其构造方法
- super(类名,self)在mro序列里找打指定类名后的第一个类调用其构造方法。
2.5 总结
- super一般是为了继承中构造函数的问题而进行的调用,通常出现在构造方法里。
- 普通函数的调用最好还是用类名直接调用。这样比较直接
3 MRO(Method Resolution Order):方法解析顺序
3.1 python 2.2版本之前经典类
所有经典类默认不继承object,并且采用的是深度优先算法。例如上面的钻石继承,在2.7之前深度优先序列就是 GrandSon-Son1-Parent-Son2。
3.2 python 2.2版本之后新式类
(python2有经典类,新式类两种类,python只3有新式类)采用的是C3算法,C3算法的具体过程是这样的
https://blog.csdn.net/u011467553/article/details/81437780
3.3 一个举例
计算过程
mro(A) =mro(A(B,C))
=[A]+merge(mro(B),mro(C),[B,C])
=[A]+merge([B,D,E,O],[C,E,F,O],[B,C])
=[A]+[B]+merge([D,E,O],[C,E,F,O],[C])
#继续判断第一个列表里的表头D
=[A]+[B,D]+merge([E,O],[C,E,F,O],[C])
#E出来失败,开始判断第二个列表里的表头C
=[A]+[B,D,C]+merge([E,O],[E,F,O])
=[A]+[B,D,C,E]+merge([O],[F,O])
=[A,B,D,C,E,F,O]
mro(B) =mro(B(D,E))
=[B]+merge(mro(D),mro(E),[D,E])
=[B]+merge([D,O],[E,O],[D,E])
=[B]+[D,E,O]
=[B,D,E,O]
mro(C) =mro(C(E,F))
=[C]+merge(mro(E),mro(F),[E,F])
=[C]+merge([E,O],[F,O],[E,F])
=[C]+[E,F,O]
=[C,E,F,O]
mro(D) =mro(D(O))
=[D]+merge(mro(O))
=[D,O]
mro(E) =mro(E(O))
=[E]+merge(mro(O))
=[E,O]
mro(F) =mro(F(O))
=[F]+merge(mro(O))
=[F,O]
3.4 mro的用处
- 在构造函数里用super()调用init方法会按照mro的顺序依次调用mro列表的后一个init方法。
- 如果多个父类或者间接父类都定义了一个方法fun,我这个类直接调用fun方法选择的就是按照mro列表查找到的类的第一个fun方法。