Python继承、super及混入类
文章目录
一、Python 的继承
1. 继承的本质
广义的世界中: 继承是指由父到子的功能的传承
Python世界中, 继承是指子类继承父类的公有方法,公有属性等。Python中继承支持单继承模式和多继承模式
2. MRO和C3算法
基于Python 的多继承, 这就产生了一个问题 ----多继承查找问题
, 多继承情况下, 子类调用父类中定义过的具有相同方法 (Python中不支持重置,所以同名称方法在一个类中只能存在一个), 需要生成一个执行查找表, 来决定最终执行的方法来源。
MRO(Method Resolution Order)即python类在查询方法的执行最终源所生成一个执行计划表, 可以通过如下方法查找:
class MroDemo:
pass
print(MroDemo.mro())
>>> [<class '__main__.MroDemo'>, <class 'object'>]
可以看出 MroDemo
的查找顺序是先类本身, 其次到基类object
查找, 因为没有继承其他的定义类, python默认只给继 承了object
在Python2.3以前, 很多资料的说法是Python在mro算法上经历过 经典算法(深度优先算法), 新式算法(过度的一种顺序序列 算法), 但是2.3 只有, 统一使用的是C3算法, C3算法
的目的是为了解决原先算法不满足本地优先级、单调性等问题。
本质上C3 算法是生成了一个查找序列, 类在查找属性的时候, 会按照这个执行序列去查找满足的属性。
C3算法通过一次次的合并, 将一个复杂的查找网络合并成一个有序的查找序列, 假如一个类A, 继承了B,C,D三个类, 这三个 类向上只继承了object,那么A的查找顺序则满足以下等式:
A.mro() :list = [A] + merge(B.mro() + C.mro() + D.mro() + [B,C,D]) # 结果为一个list
解释
-
首先, 类的查找顺序最高优先级必然是类本身, 所以最左的必然是类本身
-
其次, merge 的顺序是满足从左到右的, 这代表其次同等继承情况下, 书写在左边的优先级会更高
-
当然,最终要的还是merge这个函数,到底基于什么样的算法
示例中: 存在三次mro的调用,当然在复杂场景下, mro需要做详细的解剖的, 不过在这个示例中, 可以简单分析出B,C,D的查找序列, 所以转换为如下等式:
# 注: 余下全文下以o代替object
# 可以参照,递归树结构时, 碰到叶子节点时的场景
A.mro() :list = [A] + merge([B,o] +[C,o]+ [D,o] + [B,C,D])
# [A] -- 代表当前mro已生成查找序列
# merge([B,o] +[C,o]+ [D,o] + [B,C,D]) -- 需要通过合并依次查找出的mro预处理序列
c3 算法中, merge操作会遍历所有将执行merge操作的序列, 所有序列中的所有元素,满足以下条件:
1. 在当前序列中, 为第一个元素
2. 在剩余其他所有拥有当前元素的序列
中都是第一个元素 或者在其他所有序列中不存在(这里有异议的可以尝试, 应该会得到: Cannot create a consistent method resolution 这样的异常)
将会从所有执行merge的序列中被删除, 然后追加到当前 mro序列中:
那么这个示例的执行顺序将如下:
# 1. 原始序列
A.mro() :list = [A] + merge([B,o] +[C,o]+ [D,o] + [B,C,D])
# 2. 查找最左序列, B满足条件, 被删除
A.mro() :list = [A,B] + merge([o] +[C,o]+ [D,o] + [C,D])
# 3. 查找最左, 此时出现o,但是不满足条件, 执行到C, C满足条件
A.mro() :list = [A,B,C] + merge([o] +[o]+ [D,o] + [D])
# 4. 同样的, 第三序列中存在o但不为第一元素, 执行到D,D满足条件
A.mro() :list = [A,B,C,D] + merge([o] +[o]+ [o] + [])
# 5. 此时可以得出结果
A.mro() = [A,B,C,D,o]
测试实例如下:
class A(o):
pass
class B(o):
pass
class C(o):
pass
class E(A,B):
pass
class F(B,C):
pass
class G(E,F):
pass
# E.mro() = [E] + merge(A.mro() + B.mro() + [A,B]) = [E,A,B,o]
print(E.mro())
>>> [<class '__main__.E'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
# F.mro() = [F] + merge(B.mro()+C.mro() + [B,C]) = [F,B,C,o]
print(F.mro())
>>> [<class '__main__.F'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
# G.mro() = [G] + merge(E.mro() + F.mro() + [E,F])
# -> = [G] + merge([E,A,B,o]+[F,B,C,o]+[E,F])
# -> = [G,E,A,F,B,C,o]
print(G.mro())
>>> [<class '__main__.G'>, <class '__main__.E'>, <class '__main__.A'>, <class '__main__.F'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
3. 钻石继承(棱形继承问题)
Ok, 截止目前为止没有找到任何足以说明这个问题危害的文章, 跳过...
不过若有深入体会过其中危害的,可以联系讨论...
二、混入类Mixin构造
了解mixin 混入类之前, 首先了解Python 的super() 类。
1. 什么是super
先解释一个误区: super不是直接指向父类,super()对象调用方法也不是直接调用父类的方法
, 很多文章说, super 是python 官方提供的一种,当子类中实现了父类方法, 而子类需要调用父类的方法功能是, 可以通过super()跳过当前子类直接去父类查找对应的方法, 当然这种说法是片面的。
super是python 官方内置的一个类, 每个super对象实例, 都有实例绑定的类型和绑定的实例对象。
首先粘贴源码中提到的 super类的4种构造方法:
class SuperDemo:
def demo(self):
#1. super直接无参数构造, 绑定类为SuperDemo, 绑定对象为superdemo实例对象
super()
print(super())
>>> <super: <class 'SuperDemo'>, <SuperDemo object>>
#2. 传入类型到super, 绑定类为传入类型, 绑定对象为NULL
super(SuperDemo)
print(super(SuperDemo))
>>> <super: <class 'SuperDemo'>, NULL>
#3.传入类型和实例绑定, 绑定类型和实例为传入的类型和实例, 需要满足: isinstance(obj,type)
super(SuperDemo, self)
print(super(SuperDemo, self))
>>> <super: <class 'SuperDemo'>, <SuperDemo object>>
#4. 传入两个类型, 绑定类型为传入的第一类型, 绑定对象实例为第二个类型的实例, 需要满足:issubtype(type2,type1)
super(object, SuperDemo)
print(super(object, SuperDemo))
>>> <super: <class 'object'>, <SuperDemo object>>
2.所以,super能做什么?
常规的super调用, 我们可以认为是这样:
class SuperClass:
def say(self):
print("super say")
class Class(SuperClass):
def say(self):
super().say()
print("class say")
Class().say()
>>>super say
>>>class say
其中super().say()
的调用相当于super(Class,self).say()
, 其中super()绑定的实例为 Class() , 类为Class。
那为什么示例中,super()
调用say
方法能进入到父类的执行,这就涉及的官方的设定, 官方对此的解释为:
Typical use to call a cooperative superclass method
也就是说, super 是用于调用一个继承超类的方法,这里需要注意,一次和超类。
经过实践证明: super()是执行的mro序列当前类索引的下一个索引类的方法
class Super1:
def say(self):
print("super1 say")
class Super2:
def say(self):
print("super2 say")
class Class(Super1, Super2):
def say(self):
super().say()
print("class say")
if __name__ == '__main__':
print(Class.mro())
c = Class()
c.say()
>>> [<class '__main__.Class'>, <class '__main__.Super1'>, <class '__main__.Super2'>, <class 'object'>]
>>> super1 say
>>> class say
示例中, 当前类为Class, 下一个Mro执行类,为 super1,所以执行的super1的say
但是当前类是如何判断的?以及Mro查找序列,又应该定位为那个类的查找序列呢?
实践证明:
当前类: 是super绑定的类
当前查找序列: 是super绑定的object 的查找序列
合并起来,就是, super执行的方法为 绑定的类在绑定的实例对象的类mro查找序列中位置的 下一个索引类的方法.
示例如下:
class O:
def say(self):
print("o say")
class Super1(O):
def say(self):
print("super1 say")
class Super2(O):
def say(self):
print("super2 say")
class Class(Super1, Super2):
def say(self):
super(Super1, self).say()
super(Super2, self).say()
print("class say")
if __name__ == '__main__':
c = Class()
c.say()
>>> super2 say
>>> [<class '__main__.Super1'>, <class '__main__.O'>, <class 'object'>]
>>> o say
>>> [<class '__main__.Super2'>, <class '__main__.O'>, <class 'object'>]
>>> class say
当然更多示例, 暂时没做罗列,可以自行测试
3. Python Mixin 又是什么?
言归正传,Mixin 才是实际开发中很有含义的一种设计模式。
很多人认为Mixin 只是多继承的一种体现, 私以为,不可苟同, 一面之词。
如果仅仅以为mixin 只是为了静态继承, 动态添加, 那将毫无意义, 先看一个Mixin 的示例:
class Mixin:
def say(self):
print("before mixin say")
super().say()
print("after mixin say")
class F:
def say(self):
print("father say")
class S(Mixin, F):
pass
if __name__ == '__main__':
s = S()
s.say()
# out
>>> before mixin say
>>> father say
>>> after mixin say
如果你看过本文章mro和super 的说明, 那就不难理解这个out 的结果了, 所有mro 和super 的铺垫都是为了这一个mixin, 毕竟脱离业务逻辑谈理论的算法都是耍流氓。
在示例中的Mixin
类的say
做如下修改:
class Mixin:
def say(self):
print("before mixin say")
print(super())
super().say()
print("after mixin say")
# out
>>> before mixin say
>>> <super: <class 'Mixin'>, <S object>>
>>> father say
>>> after mixin say
OK, 很好的印证了前文的说法.
4. Mixin又能做什么?
如果你需要动态的为某些源码库的方法, 添加一些AOP 类似的功能, 这个源码你可以由于种种原因, 不能去修改, 那mixin 就能派上用场了。上文示例中Mixin 类就针对 F的 say
做了封装, 在不修改F 类的情况下, 插入到子类的Mro链中, 并且还能保留F 类的say
方法原有的逻辑。
最贴近开发的就是 django rest framework 的viewsetmixin 集合类, 针对http method做的一些包装。