【问题标题】:Python Multiple Inheritance: call super on allPython多重继承:全部调用super
【发布时间】:2015-05-20 15:02:00
【问题描述】:

我有以下两个超类:

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

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

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

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

Pythonic 的方法是什么?谢谢。

【问题讨论】:

标签: python


【解决方案1】:

执行摘要:

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

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

下面介绍的使用__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,并在不知道类名的情况下遍历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

如果有可能基类之一没有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 定义中定义的每个类层次结构。即,它将遍历每个前辈类,直到 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

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

如果您想要每个前辈的on_start 方法,您可以使用__mro__ 并过滤掉没有on_start 的类作为该类的__dict__ 的一部分。否则,你可能会得到一个前辈的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

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

正如雷蒙德·赫廷格所说:

super() 负责将方法调用委托给某个类 实例的祖先树。为了使可重新排序的方法调用正常工作, 这些类需要合作设计。这提出了三个 容易解决的实际问题:

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

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

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

解决方案是编写协作类,通过祖先列表统一使用super 或创造性地使用adapter pattern 来适配你无法控制的类。这些方法在 Raymond Hettinger 的文章Python’s super() considered super! 中有更完整的讨论。

【讨论】:

  • 请问为什么投反对票以及如何改进这个答案?
  • 拒绝投票,因为这对super 将调用哪个方法提供了误导性的解释,并且没有解释如何正确地进行协作多重继承。特别是,如果您尝试从循环 __mro__ 的类中进行多重继承,那么您调用所有祖先方法的选项会很糟糕;特别是,再次循环 __mro__ 会导致某些方法被调用两次。
  • 它给人的印象是,Foo 类中的super 调用会查看Foo 的MRO,并且总是调用Foo 的祖先,而不是查看@ 的MRO 987654378@ 并可能调用在Foo 的祖先中无处出现的方法。此外,如果我们有class B(C):class A(B):,并且A.on_startB.on_start 都循环通过它们的__mro__,那么C.on_start 将被A.on_startB.on_start 调用。
  • 我同意你说的。但是,我认为这超出了这个问题的范围,而且我认为我的回答没有误导性。关于使用__bases__ 调用on_start 的每个父母的mro 的部分是对问题的回应。任何从多重继承中获得多个属性的解决方案的方法都充满了问题:“正确”的顺序是什么?如果链中没有祖先不具有on_start 属性怎么办?OP 应该只重写基类以协作响应多个“on_start”调用,但这不是问题。
  • 你应该包括一个关于如何重写基类的部分。该问题并未以排除它的方式陈述,您的回答给人的印象是它所陈述的解决方案是“正确的事情”,而不是处理非合作基类的解决方法。
【解决方案2】:
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):
        super(Child, self).on_start()
        super(Parent1, self).on_start()

c = Child()

c.on_start()
do something
do something else

或者没有超级:

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

【讨论】:

    【解决方案3】:

    在您的情况下,由于两个父母都实现了相同的方法,super 将与继承的第一个父母相同,从左到右(对于您的代码,Parent1)。用super 调用两个函数是不可能的。做你想做的事,你必须简单地从父类调用方法,如下:

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

    【讨论】:

      猜你喜欢
      • 2018-12-08
      • 2017-07-30
      • 2016-02-25
      • 1970-01-01
      • 2018-11-06
      • 2020-07-05
      • 2014-07-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多