Метод Python super и альтернативы вызова

Я вижу везде примеры, что методы суперкласса должны быть вызваны:

super(SuperClass, instance).method(args)

Есть ли недостаток:

SuperClass.method(instance, args)

Ответ 1

Рассмотрим следующую ситуацию:

class A(object):
    def __init__(self):
        print('Running A.__init__')
        super(A,self).__init__()
class B(A):
    def __init__(self):
        print('Running B.__init__')        
        # super(B,self).__init__()
        A.__init__(self) 

class C(A):
    def __init__(self):
        print('Running C.__init__')
        super(C,self).__init__()
class D(B,C):
    def __init__(self):
        print('Running D.__init__')
        super(D,self).__init__()

foo=D()

Итак, классы образуют так называемый алмаз наследования:

    A
   / \
  B   C
   \ /
    D

Запуск кода дает

Running D.__init__
Running B.__init__
Running A.__init__

Это плохо, потому что C __init__ пропущен. Причина этого в том, что B __init__ вызывает A __init__ напрямую.

Цель super - разрешить алмазы наследования. Если вы не комментируете

# super(B,self).__init__()

и комментарий

A.__init__(self) 

код дает более желательный результат:

Running D.__init__
Running B.__init__
Running C.__init__
Running A.__init__

Теперь все методы __init__ вызываются. Обратите внимание, что в то время, когда вы определяете B.__init__, вы можете подумать, что super(B,self).__init__() совпадает с вызовом A.__init__(self), но вы ошибаетесь. В приведенной выше ситуации super(B,self).__init__() на самом деле вызывает C.__init__(self).

Святые курит, B ничего не знает о C, и тем не менее super(B,self) знает, чтобы позвонить C __init__? Причина в том, что self.__class__.mro() содержит C. Другими словами, self (или выше, foo) знает о C.

Поэтому будьте осторожны - они не являются взаимозаменяемыми. Они могут дать совершенно разные результаты.

Использование super имеет подводные камни. Он требует значительного уровня координации между всеми классами диаграммы наследования. (Они должны, например, иметь одну и ту же сигнатуру вызова для __init__, так как любой конкретный __init__ не знал, какой другой __init__ super может вызвать следующий, или else использовать **kwargs.) Кроме того, вы должны быть совместимы с использованием super везде. Пропустите его один раз (как в приведенном выше примере), и вы победите цель цели super. См. Ссылку для других подводных камней.

Если у вас есть полный контроль над иерархией классов или вы избегаете алмазов наследования, тогда нет необходимости в super.

Ответ 2

Нет никакого штрафа как есть, хотя ваш пример несколько ошибочен. В первом примере это должно быть

super(SubClass, instance).method(args)  # Sub, not SuperClass

и это приводит меня к цитированию Документов Python:

Существует два типичных варианта использования для super. В иерархии классов с одиночным наследованием super может использоваться для обозначения родительских классов без их явного указания, что делает код более удобным для обслуживания. Это использование тесно совпадает с использованием super в других языках программирования.

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

В основном, используя первый метод, вам не нужно жестко программировать свой родительский класс для иерархических классов одного класса, и вы просто не можете делать то, что хотите (эффективно/эффективно), используя второй метод при использовании множественного наследования.