Python继承、super及混入类

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

解释

  1. 首先, 类的查找顺序最高优先级必然是类本身, 所以最左的必然是类本身

  2. 其次, merge 的顺序是满足从左到右的, 这代表其次同等继承情况下, 书写在左边的优先级会更高

  3. 当然,最终要的还是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做的一些包装。

参考链接:

stack over - python super()

Python’s super() considered super!

python mro 原理

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值