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

时间:2015-05-20 15:02:00

标签: python

我有以下两个超类:

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的做法是什么?感谢。

3 个答案:

答案 0 :(得分:17)

执行摘要:

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

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会解析只有Parent3具有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()

如果要按方法名称调用多个父方法,则可以在这种情况下使用__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定义中定义的每个类层次结构。也就是说,它将通过每个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

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

enter image description here

如果您想要每个forbearer的on_start方法,您可以使用__mro__并过滤掉那些没有on_start的类作为__dict__的一部分类。否则,您可能会获得forbearer的on_start方法。换句话说,hassattr(c, 'on_start')对于True是后代的每个类都是Child(在这种情况下除了object),因为Ghengison_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__链。

如Raymond Hettinger所述:

  

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

     

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

     

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

     

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

解决方案是编写合作类,通过祖先列表统一使用super或创建使用__mro__来适应您无法控制的类。 Raymond Hettinger在文章adapter pattern中更全面地讨论了这些方法。

答案 1 :(得分:1)

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)

答案 2 :(得分:0)

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

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