Python多重继承:在所有上调用super

i_t*_*ope 10 python

我有以下两个超类:

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

class Parent2(object):
    def on_start(self):
        print('do something else')
Run Code Online (Sandbox Code Playgroud)

我希望有一个继承的子类可以为父母双方打电话.

class Child(Parent1, Parent2):
    def on_start(self):
        # super call on both parents
Run Code Online (Sandbox Code Playgroud)

什么是Pythonic的方法呢?谢谢.

daw*_*awg 17

执行摘要:

Super只根据类层次结构执行一种方法__mro__.如果要使用相同的名称执行多个方法,则需要编写父类来协同执行(通过super隐式或显式调用),或者需要循环遍历子类__bases____mro__值.

我们的工作super是将一部分或全部方法调用委托给祖先树中的一些现有方法.代表团可能会在您控制的课程之外进行.委派的方法名称需要存在于基类组中.

下面使用__bases__with的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()
Run Code Online (Sandbox Code Playgroud)

在这种情况下,Child有三个直接的父母.只有一个Parent3有一个on_start方法.调用superParent3具有的结果on_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()
Run Code Online (Sandbox Code Playgroud)

如果要按方法名称调用多个父方法,则可以__bases__在这种情况下使用而不是super,并在Child不知道类名的情况下迭代基类:

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
Run Code Online (Sandbox Code Playgroud)

如果有可能有一个基类没有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"
Run Code Online (Sandbox Code Playgroud)

使用__bases__将对super定义中Child定义的每个类层次结构起类似作用.即,它会虽然每个forbearer类,直到on_start满足一次为每个类的父:

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
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

如果你想要每一个forbearer的on_start方法,你可以使用__mro__并过滤掉那些没有on_start作为__dict__该类的一部分的类.否则,你可能会得到一个forbearer的on_start方法.换句话说,hassattr(c, 'on_start')True对每个类Child是从后代(除了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
Run Code Online (Sandbox Code Playgroud)

但这也有一个问题 - 如果Child进一步子类化,那么Child的孩子也会循环到同一个__mro__链上.

如Raymond Hettinger所述:

super()是将方法调用委托给实例的祖先树中的某个类的业务.对于可重新排序的方法调用,需要合作设计类.这提出了三个容易解决的实际问题:

1)super()调用的方法需要存在

2)调用者和被调用者需要具有匹配的参数签名和

3)每次出现的方法都需要使用super()

解决方案是编写合并类,super通过祖先列表或创建使用适配器模式统一使用,以适应您无法控制的类.这些方法在Python的super()文章中被更全面地讨论了!作者:Raymond Hettinger.

  • 否决,因为这对“super”将调用哪个方法给出了误导性的解释,并且没有解释如何正确地进行协作多重继承。特别是,如果您尝试从循环遍历“__mro__”的类中进行多重继承,则调用所有祖先方法的选项会很糟糕;特别是,再次循环`__mro__`将导致某些方法被调用两次。 (2认同)
  • 它给人的印象是类“Foo”中的“super”调用将查看“Foo”的MRO并始终调用“Foo”的祖先,而不是查看“type(self)”的MRO并可能调用在 `Foo` 的祖先中没有出现过的方法。另外,如果我们有“class B(C):”和“class A(B):”,并且“A.on_start”和“B.on_start”都循环通过它们的“__mro__”,那么“C.on_start”将由“A.on_start”和“B.on_start”调用。 (2认同)