Подтвердить что ты не робот

Множественное наследование python из разных путей с тем же именем метода

В следующем примере кода может использоваться super, или C должен явно вызывать A.foo и B.foo?

class A(object):
    def foo(self):
        print 'A.foo()'

class B(object):
    def foo(self):
        print 'B.foo()'

class C(A, B):
    def foo(self):
        print 'C.foo()'
        A.foo(self)
        B.foo(self)
4b9b3361

Ответ 1

super() будет разрешать только один тип класса для данного метода, поэтому, если вы наследуете от нескольких классов и хотите вызвать метод в обоих из них, вам нужно сделать это явно. то есть A.foo(self)

Ответ 2

super действительно предназначен для этой ситуации, но он работает только в том случае, если вы используете его последовательно. Если базовые классы не все также используют super, это не сработает, и если метод не находится в object, вы должны использовать что-то вроде общего базового класса для завершения цепочки вызовов super.

class FooBase(object):
    def foo(self): pass

class A(FooBase):
    def foo(self):
        super(A, self).foo()
        print 'A.foo()'

class B(FooBase):
    def foo(self):
        super(B, self).foo()
        print 'B.foo()'

class C(A, B):
    def foo(self):
        super(C, self).foo()
        print 'C.foo()'

@Marcin спрашивает, почему должна быть общая база:

Без FooBase, который реализует foo, но не вызывает super(), последний класс, вызывающий super(), получит ошибку атрибута, так как нет базового метода для вызова.

Если бы существовали отдельные базовые классы class A(AFooBase): и class B(BFooBase):, вызов super() в A вызывал метод в AFooBase, и метод в B никогда не вызывался. Когда база является общей для всех классов, она подходит к концу порядка разрешения метода, и вы можете быть уверены, что независимо от того, как будут определены классы, метод базового класса будет последним.

Ответ 3

Спасибо всем, кто внес вклад в эту тему.

Подвести итоги:

  • (В настоящее время) принятый ответ является неточным. Правильное описание должно быть таким: super() НЕ ТОЛЬКО хорош для разрешения одиночного наследования, НО ТАКЖЕ множественного наследования. И причина хорошо объяснена в комментарии @blckknght:

    Хотя явный вызов методов базового класса может работать для очень простых сценариев, таких как пример с вопросом, он сломается, если сами базовые классы наследуют от общей базы, и вы не хотите, чтобы метод базового класса вызывался дважды. Это известно как "наследование алмазов" и является большой проблемой для многих систем множественного наследования (как в C++). Совместное множественное наследование Python (с super()) позволяет вам во многих случаях легко его решить (хотя нельзя сказать, что кооперативную иерархию множественного наследования легко спроектировать или это всегда хорошая идея).

  • Правильный способ, как указал @duncan, - это использовать super(), но использовать его последовательно.

    super действительно предназначен для этой ситуации, но он работает, только если вы используете его последовательно. Если базовые классы также не все используют super он не будет работать, и если метод не находится в object вы должны использовать что-то вроде общего базового класса для завершения цепочки super вызовов.

    class FooBase(object):
        def foo(self): pass
    
    class A(FooBase):
        def foo(self):
            super(A, self).foo()
            print 'A.foo()'
    
    class B(FooBase):
        def foo(self):
            super(B, self).foo()
            print 'B.foo()'
    
    class C(A, B):
        def foo(self):
            super(C, self).foo()
            print 'C.foo()'
    
    C().foo()  # Run this
    

    Но также стоит отметить, что порядок вызова метода НЕ может показаться интуитивным с первого взгляда. Результат:

    B.foo()
    A.foo()
    C.foo()
    

    Этот, казалось бы, странный порядок. Фактическим порядком вызова по-прежнему является C, A, B, основанный на MRO. Другими словами,

    super() вызовет метод foo для "первого" "следующего" суперкласса. Это основано на __mro__ разрешения методов (__mro__) для класса C

    - Цитируется и модифицируется от ответа @Manoj-Govindan

    >>> C.__mro__
    (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
    >>> 
    
  • Как правило, если вы хотите вернуться к ВСЕМ родительским методам, но на самом деле не заботитесь о порядке вызова, используйте super() согласованно. В противном случае вы можете явно вызывать родительские методы в определенном порядке.

  • Не смешивайте использование super() и явных вызовов. В противном случае вы получите неприятное дублирование, как упомянуто в этом ответе.

ОБНОВЛЕНИЕ: Если вы хотите погрузиться глубже...

Короче говоря, использование super(...) последовательно во всем семействе классов обеспечит ВСЕ методы с одинаковыми именами от предков, вызываемые один раз, в порядке MRO. Такое поведение call-ALL-ancestors (а не call-only-the-first-кандидата) может быть концептуально легче принять, если случается, что метод является __init__(), см. Пример в этом сообщении в блоге.

Однако сказать "следуй приказу MRO" может быть не совсем точно. Это действительно всегда следует за "внуком" MRO, так или иначе. Приятно видеть это в действии. Результат следующей программы может быть не совсем таким, как вы думали. Обратите внимание на то, как A.__mro__ остается одинаковым в 2 разных A.__mro__ вызовов, но как super(A, self).name или super(A, self).foo() ведут себя РАЗНО при запуске A().foo() и C().foo(). Смотрите результат, указанный в конце.

class FooBase(object):
    name = "FooBase"
    def foo(self):
        print('         Base.foo() begins')
        print("         My name is: %s" % self.name)
        print("         My super name is not available")
        print('         Base.foo() ends')

class A(FooBase):
    name = "A"
    def foo(self):
        print('     A.foo() begins')
        print("     My name is: %s" % self.name)
        print("     My super name is: %s" % super(A, self).name)
        print("     A.__mro__ is %s" % str(A.__mro__))
        super(A, self).foo()
        print('     A.foo() ends')

class B(FooBase):
    name = "B"
    def foo(self):
        print('     B.foo() begins')
        print("     My name is: %s" % self.name)
        print("     My super name is: %s" % super(B, self).name)
        print("     B.__mro__ is %s" % str(B.__mro__))
        super(B, self).foo()
        print('     B.foo() ends')

class C(A, B):
    name = "C"
    def foo(self):
        print 'C.foo() begins'
        print("My name is: %s" % self.name)
        print("My super name is: %s" % super(C, self).name)
        print(" C.__mro__ is %s" % str(C.__mro__))
        super(C, self).foo()
        print('C.foo() ends')


print("We will call A.foo()")
A().foo()

print("We will call C.foo()")
C().foo()  # Run this to see how each foo() is called ONLY ONCE

И его результат из Python 2.7.12:

We will call A.foo()
     A.foo() begins
     My name is: A
     My super name is: FooBase
     A.__mro__ is (<class '__main__.A'>, <class '__main__.FooBase'>, <type 'object'>)
         Base.foo() begins
         My name is: A
         My super name is not available
         Base.foo() ends
     A.foo() ends
We will call C.foo()
C.foo() begins
My name is: C
My super name is: A
 C.__mro__ is (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.FooBase'>, <type 'object'>)
     A.foo() begins
     My name is: C
     My super name is: B
     A.__mro__ is (<class '__main__.A'>, <class '__main__.FooBase'>, <type 'object'>)
     B.foo() begins
     My name is: C
     My super name is: FooBase
     B.__mro__ is (<class '__main__.B'>, <class '__main__.FooBase'>, <type 'object'>)
         Base.foo() begins
         My name is: C
         My super name is not available
         Base.foo() ends
     B.foo() ends
     A.foo() ends
C.foo() ends

Ответ 4

Супер вызовет метод foo в "первом" суперклассе. Это основано на порядке разрешения метода (__mro__) для класса C.

>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
>>> 

Поэтому, если вы вызываете super(C, self).foo(), вызывается A.foo. Если вы измените порядок наследования на class C(B, A):, то это будет отменено. __mro__ теперь выглядит следующим образом:

>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
>>> 

Если вы выберете super(C, self).foo() после этого изменения, вызов будет вызван B.foo().

Ответ 5

Если вы добавили следующее:

class A(object):
    def foo(self):
        print 'A.foo()'

class B(object):
    def foo(self):
        print 'B.foo()'

class C(A, B):
    def foo(self):
        super(C, self).foo()
        print 'C.foo()'
        A.foo(self)
        B.foo(self)


c = C()
c.foo()

Тогда super (C, self).foo() относится к A.foo

Выходной сигнал

A.foo()
C.foo()
A.foo()
B.foo()

[Изменить: включить дополнительную информацию и ссылки]

Ответ 6

Как сказал Дункан выше, вы можете получить предсказуемые результаты, особенно если вы используете super() последовательно.

Результаты из следующего теста были полезны для меня:

class FooBase(object):
    def foo(self):
        print 'FooBase.foo()'

class A(FooBase):
    def foo(self):
        print 'A.foo() before super()'
        super(A, self).foo()
        print 'A.foo() after super()'

class B(FooBase):
    def foo(self):
        print 'B.foo() before super()'
        super(B, self).foo()
        print 'B.foo() after super()'

class C(A, B):
    def foo(self):
        print 'C.foo() before super()'
        super(C, self).foo()
        print 'C.foo() after super()'

Это напечатает:

>>> c = C()
>>> c.foo()
C.foo() before super()
A.foo() before super()
B.foo() before super()
FooBase.foo()
B.foo() after super()
A.foo() after super()
C.foo() after super()

Ответ 7

Это возможно с помощью super

class A(object):
    def foo(self):
        print 'A.foo()'

class B(object):
    def foo(self):
        print 'B.foo()'

class C(A, B):
    def foo(self):
        print 'C.foo()'
        super(C, self).foo() # calls A.foo()
        super(A, self).foo() # calls B.foo()

Ответ 8

class A:
    def article(self):
        print("Class A")

class B:
    def article(self):
        print("Class B")

class C(A,B):
    def article(self):
        print("Class C ::\n")
        super().article()

x = C()
x.article()

выход:

Класс С ::

Класс А