Python 多重继承 : call super on all

标签 python

我有以下两个父类(super class):

class Parent1(object):
    def on_start(self):
        print('do something')

class Parent2(object):
    def on_start(self):
        print('do something else')

我希望有一个继承自两者的子类能够为 parent 双方调用 super。

class Child(Parent1, Parent2):
    def on_start(self):
        # super call on both parents

执行此操作的 Pythonic 方法是什么?谢谢。

最佳答案

执行摘要:

Super仅根据类层次结构的 __mro__ 执行一个方法.如果你想执行一个以上的同名方法,你的父类需要编写以合作执行此操作(通过隐式或显式调用 super)或者你需要循环 __bases____mro__子类的值。

super 的工作是将部分或全部方法调用委托(delegate)给类祖先树中的某个现有方法。 委派可能会在您控制的类之外进行得很好。委托(delegate)的方法名需要存在于基类组中。

下面介绍的使用 __bases__try/except 的方法最接近于如何调用每个父级的同名方法的问题的完整答案。


super 在您想调用父方法之一但不知道哪个父方法的情况下很有用:

class Parent1(object):
    pass

class Parent2(object):
    # if Parent 2 had on_start - it would be called instead 
    # because Parent 2 is left of Parent 3 in definition of Child class
    pass

class Parent3(object):
    def on_start(self):
        print('the ONLY class that has on_start')        

class Child(Parent1, Parent2, Parent3):
    def on_start(self):
        super(Child, self).on_start()

在这种情况下,Child 有三个直系父级。只有一个 Parent3 具有 on_start 方法。调用 super 解析只有 Parent3on_start 并且这是被调用的方法。

如果 Child 继承自多个具有 on_start 方法的类,则顺序是从左到右(如类定义中所列)和从下到上解析的(作为逻辑继承)。 仅调用其中一个方法,类层次结构中的其他同名方法已被取代。

所以,更常见的是:

class GreatGrandParent(object):
    pass

class GrandParent(GreatGrandParent):
    def on_start(self):
        print('the ONLY class that has on_start')

class Parent(GrandParent):
    # if Parent had on_start, it would be used instead
    pass        

class Child(Parent):
    def on_start(self):
        super(Child, self).on_start()

如果你想通过方法名调用多个父类方法,在这种情况下你可以使用__bases__而不是super,并在不知道按名称分类:

class Parent1(object):
    def on_start(self):
        print('do something')

class Parent2(object):
    def on_start(self):
        print('do something else')

class Child(Parent1, Parent2):
    def on_start(self):
        for base in Child.__bases__:
            base.on_start(self)

>>> Child().on_start()
do something
do something else

如果有可能其中一个基类没有on_start,您可以使用try/except:

class Parent1(object):
    def on_start(self):
        print('do something')

class Parent2(object):
    def on_start(self):
        print('do something else')

class Parent3(object):
    pass        

class Child(Parent1, Parent2, Parent3):
    def on_start(self):
        for base in Child.__bases__:
            try:
                base.on_start(self)
            except AttributeError:
                # handle that one of those does not have that method
                print('"{}" does not have an "on_start"'.format(base.__name__))

>>> Child().on_start()
do something
do something else
"Parent3" does not have an "on_start"

使用 __bases__ 的行为类似于 super 但针对 Child 定义中定义的每个类层次结构。即,它会遍历每个 forbearer 类,直到 on_start 满足 once 类的每个父类:

class GGP1(object):
    def on_start(self):
        print('GGP1 do something')

class GP1(GGP1):
    def on_start(self):
        print('GP1 do something else')

class Parent1(GP1):
    pass        

class GGP2(object):
    def on_start(self):
        print('GGP2 do something')

class GP2(GGP2):
    pass

class Parent2(GP2):
    pass            

class Child(Parent1, Parent2):
    def on_start(self):
        for base in Child.__bases__:
            try:
                base.on_start(self)
            except AttributeError:
                # handle that one of those does not have that method
                print('"{}" does not have an "on_start"'.format(base.__name__))

>>> Child().on_start()
GP1 do something else
GGP2 do something
# Note that 'GGP1 do something' is NOT printed since on_start was satisfied by 
# a descendant class L to R, bottom to top

现在想象一个更复杂的继承结构:

enter image description here

如果你想要每个 forbearer 的 on_start 方法,你可以使用 __mro__ 并过滤掉没有 on_start 作为一部分的类他们的 __dict__ 该类的。否则,您可能会获得 forbearer 的 on_start 方法。换句话说,hassattr(c, 'on_start') 对于 Child 是其后代的每个类都是 True(object 除外 在这种情况下)因为 Ghengis 有一个 on_start 属性并且所有类都是 Ghengis 的后代类。

** 警告——仅限演示 **

class Ghengis(object):
    def on_start(self):
        print('Khan -- father to all')    

class GGP1(Ghengis):
    def on_start(self):
        print('GGP1 do something')

class GP1(GGP1):
    pass

class Parent1(GP1):
    pass        

class GGP2(Ghengis):
    pass

class GP2(GGP2):
    pass

class Parent2(GP2):
    def on_start(self):
        print('Parent2 do something')

class Child(Parent1, Parent2):
    def on_start(self):
        for c in Child.__mro__[1:]:
            if 'on_start' in c.__dict__.keys():
                c.on_start(self)

>>> Child().on_start()
GGP1 do something
Parent2 do something
Khan -- father to all

但这也有一个问题——如果 Child 被进一步子类化,那么 Child 的 child 也会循环遍历相同的 __mro__ 链。

正如 Raymond Hettinger 所说:

super() is in the business of delegating method calls to some class in the instance’s ancestor tree. For reorderable method calls to work, the classes need to be designed cooperatively. This presents three easily solved practical issues:

1) the method being called by super() needs to exist

2) the caller and callee need to have a matching argument signature and

3) every occurrence of the method needs to use super()

解决方案是编写协作类,通过祖先列表或创造性地使用adapter pattern统一使用super适应你无法控制的类(class)。这些方法在文章 Python’s super() considered super! 中进行了更完整的讨论。由雷蒙德·赫廷格 (Raymond Hettinger) 着。

关于Python 多重继承 : call super on all,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30353498/

相关文章:

python - 从列表中删除镜像对立面的最快方法

python - 在python中解析xml文件的数据

Python 类成员延迟初始化

c++ - Qt + 无框窗 + 合成

python - Yaml 转储 python 字典作为不带单引号的映射

java - 从 Java 调用 python 模块时出错

python - 有效地计算 python/numpy 中许多位串的平均位?

python - 使用python在文本文件中查找单词出现的第n个实例

python - 从数字列表创建模式

python - 评估数组中的 k 个相邻(正或负)元素