Что делает 'super' в Python? - разница между super().__init__() и явным суперклассом __init__()
В чем разница между:
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
и:
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
Я видел, что super
довольно часто используется в классах с единственным наследованием. Я понимаю, почему вы используете это при множественном наследовании, но мне неясно, в чем преимущества его использования в такого рода ситуации.
Этот вопрос касается технических деталей реализации и различия между различными способами доступа к методу базового класса __init__
. Чтобы закрыть повторяющиеся вопросы, где OP просто пропускает super
вызов и спрашивает, почему атрибуты базового класса недоступны, пожалуйста, используйте Почему экземпляры моего подкласса не содержат атрибутов из базового класса (вызывая AttributeError при попытке их использования)? вместо этого.
Переведено автоматически
Ответ 1
В чем разница?
SomeBaseClass.__init__(self)
означает вызывать SomeBaseClass
's __init__
. в то время как
super().__init__()
означает вызов привязки __init__
из родительского класса, который следует за SomeBaseClass
дочерним классом (тем, который определяет этот метод) в порядке разрешения метода экземпляра (MRO).
Если экземпляр является подклассом этого дочернего класса, следующим в MRO может быть другой родительский класс.
Объясняется просто
Когда вы пишете класс, вы хотите, чтобы другие классы могли его использовать. super()
упрощает использование класса, который вы пишете, другими классами.
Как говорит Боб Мартин, хорошая архитектура позволяет отложить принятие решения как можно дольше.
super()
может обеспечить такую архитектуру.
Когда другой класс создает подкласс класса, который вы написали, он также может наследоваться от других классов. И у этих классов может быть значение, __init__
которое следует после этого __init__
, основанное на порядке следования классов для разрешения метода.
Без super
вы, вероятно, жестко запрограммировали бы родительский элемент класса, который вы пишете (как в примере). Это означало бы, что вы не будете вызывать next __init__
в MRO , и, таким образом, вы не сможете повторно использовать код в нем.
Если вы пишете свой собственный код для личного использования, вам может быть наплевать на это различие. Но если вы хотите, чтобы ваш код использовали другие, использование super
- это то, что обеспечивает большую гибкость для пользователей кода.
Python 2 против 3
Это работает в Python 2 и 3:
super(Child, self).__init__()
Это работает только в Python 3:
super().__init__()
Он работает без аргументов, перемещаясь вверх по фрейму стека и получая первый аргумент метода (обычно self
для метода экземпляра или cls
для метода класса - но могут быть и другие имена) и находя класс (например, Child
) в свободных переменных (он ищется с именем __class__
как свободная закрывающая переменная в методе).
Раньше я предпочитал демонстрировать кросс-совместимый способ использования super
, но теперь, когда Python 2 в значительной степени устарел, я продемонстрирую способ выполнения действий на Python 3, то есть вызов super
без аргументов.
Косвенное обращение с прямой совместимостью
Что это вам дает? Для одиночного наследования примеры из вопроса практически идентичны с точки зрения статического анализа. Однако использование super
дает вам уровень косвенности с прямой совместимостью.
Прямая совместимость очень важна для опытных разработчиков. Вы хотите, чтобы ваш код продолжал работать с минимальными изменениями по мере того, как вы его изменяете. Когда вы смотрите на историю своих изменений, вы хотите точно видеть, что когда изменилось.
Вы можете начать с одиночного наследования, но если вы решите добавить другой базовый класс, вам нужно будет изменить только строку с основаниями - если базы изменятся в классе, от которого вы наследуете (скажем, добавлен миксин), вы ничего не измените в этом классе.
В Python 2 получение аргументов в super
и правильных аргументов метода может немного сбивать с толку, поэтому я предлагаю использовать для его вызова только метод Python 3.
Если вы знаете, что используете super
правильно с одиночным наследованием, это облегчает дальнейшую отладку.
Внедрение зависимостей
Другие люди могут использовать ваш код и вводить родительские элементы в разрешение метода:
class SomeBaseClass(object):
def __init__(self):
print('SomeBaseClass.__init__(self) called')
class UnsuperChild(SomeBaseClass):
def __init__(self):
print('UnsuperChild.__init__(self) called')
SomeBaseClass.__init__(self)
class SuperChild(SomeBaseClass):
def __init__(self):
print('SuperChild.__init__(self) called')
super().__init__()
Допустим, вы добавляете другой класс к своему объекту и хотите внедрить класс между Foo и Bar (для тестирования или по какой-либо другой причине):
class InjectMe(SomeBaseClass):
def __init__(self):
print('InjectMe.__init__(self) called')
super().__init__()
class UnsuperInjector(UnsuperChild, InjectMe): pass
class SuperInjector(SuperChild, InjectMe): pass
При использовании дочернего элемента un-super не удается внедрить зависимость, потому что дочерний элемент, который вы используете, жестко запрограммировал метод, который будет вызываться после его собственного:
>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called
Однако класс с дочерним элементом, который использует super
, может правильно внедрить зависимость:
>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called
Обращение к комментарию
Зачем вообще это было бы полезно?
Python линеаризирует сложное дерево наследования с помощью алгоритма линеаризации C3 для создания порядка разрешения метода (MRO).
Мы хотим, чтобы методы просматривались в таком порядке.
Для метода, определенного в родительском методе, чтобы найти следующий в этом порядке без super
, ему пришлось бы
- получаем mro из типа экземпляра
- ищите тип, который определяет метод
- найдите следующий тип с помощью метода
- привяжите этот метод и вызовите его с ожидаемыми аргументами
У
UnsuperChild
не должно быть доступа кInjectMe
. Почему не вывод "Всегда избегайте использованияsuper
"? Чего я здесь не понимаю?
У UnsuperChild
нет доступа к InjectMe
. Это UnsuperInjector
который имеет доступ к InjectMe
- и все же не может вызвать метод этого класса из метода, от которого он наследуется UnsuperChild
.
Оба дочерних класса намерены вызывать метод с тем же именем, который идет следующим в MRO, который может быть другим классом, о котором он не знал при его создании.
Тот, у которого нет super
жесткого кода родительского метода - таким образом, он ограничивает поведение своего метода, и подклассы не могут внедрять функциональность в цепочку вызовов.
Тот, у которого есть super
, обладает большей гибкостью. Цепочку вызовов методов можно перехватить и внедрить функциональность.
Вам может не понадобиться эта функциональность, но подклассам вашего кода может понадобиться.
Заключение
Всегда используйте super
для ссылки на родительский класс вместо его жесткого кодирования.
Вы намерены ссылаться на родительский класс, который является следующим в строке, а не конкретно на тот, от которого, как вы видите, наследует дочерний класс.
Неиспользование super
может наложить ненужные ограничения на пользователей вашего кода.
Ответ 2
Преимущества super()
при одиночном наследовании минимальны - в основном, вам не нужно жестко кодировать имя базового класса в каждом методе, использующем его родительские методы.
Однако практически невозможно использовать множественное наследование без super()
. Сюда входят такие распространенные идиомы, как миксины, интерфейсы, абстрактные классы и т.д. Это распространяется на код, который позже расширит ваш. Если бы кто-то позже захотел написать расширенный класс Child
и mixin , его код не работал бы должным образом.
Ответ 3
Я немного поиграл с super()
и понял, что мы можем изменять порядок вызова.
Например, у нас есть следующая иерархическая структура:
A
/ \
B C
\ /
D
В этом случае MRO из D будет (только для Python 3):
In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)
Давайте создадим класс, в котором super()
вызовы выполняются после выполнения метода.
In [23]: class A(object): # or with Python 3 can define class A:
...: def __init__(self):
...: print("I'm from A")
...:
...: class B(A):
...: def __init__(self):
...: print("I'm from B")
...: super().__init__()
...:
...: class C(A):
...: def __init__(self):
...: print("I'm from C")
...: super().__init__()
...:
...: class D(B, C):
...: def __init__(self):
...: print("I'm from D")
...: super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A
A
/ ⇖
B ⇒ C
⇖ /
D
Итак, мы можем видеть, что порядок разрешения такой же, как в MRO. Но когда мы вызываем super()
в начале метода:
In [21]: class A(object): # or class A:
...: def __init__(self):
...: print("I'm from A")
...:
...: class B(A):
...: def __init__(self):
...: super().__init__() # or super(B, self).__init_()
...: print("I'm from B")
...:
...: class C(A):
...: def __init__(self):
...: super().__init__()
...: print("I'm from C")
...:
...: class D(B, C):
...: def __init__(self):
...: super().__init__()
...: print("I'm from D")
...: d = D()
...:
I'm from A
I'm from C
I'm from B
I'm from D
У нас другой порядок, это обратный порядку кортежа MRO.
A
/ ⇘
B ⇐ C
⇘ /
D
Для дополнительного чтения я бы порекомендовал следующие ответы:
Ответ 4
Разве все это не предполагает, что базовый класс является классом нового стиля?
class A:
def __init__(self):
print("A.__init__()")
class B(A):
def __init__(self):
print("B.__init__()")
super(B, self).__init__()
Не будет работать в Python 2. class A
должен быть выполнен в новом стиле, т.е: class A(object)