Что "супер" делает в Python?
В чем разница между:
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
а также:
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
я видел super
используется довольно часто в классах с единственным наследованием. Я могу понять, почему вы используете его в множественном наследовании, но неясно, каковы преимущества его использования в такой ситуации.
11 ответов
Преимущества super()
в одиночном наследовании они минимальны - в основном вам не нужно жестко кодировать имя базового класса в каждом методе, который использует его родительские методы.
Однако почти невозможно использовать множественное наследование без super()
, Это включает в себя общие идиомы, такие как миксины, интерфейсы, абстрактные классы и т. Д. Это распространяется на код, который позже расширяет ваш. Если кто-то позже хотел написать класс, который расширил Child
и миксин, их код не будет работать должным образом.
Какая разница?
SomeBaseClass.__init__(self)
значит звонить SomeBaseClass
"s __init__
, в то время как
super(Child, self).__init__()
значит позвонить __init__
из родительского класса, который следует Child
в Порядке разрешения метода экземпляра (MRO).
Если экземпляр является подклассом Child, то в MRO может быть другой родитель.
Объяснил просто
Когда вы пишете класс, вы хотите, чтобы другие классы могли использовать его. super()
позволяет другим классам использовать класс, который вы пишете.
Как говорит Боб Мартин, хорошая архитектура позволяет вам откладывать принятие решений как можно дольше.
super()
может включить такую архитектуру.
Когда другой класс наследует класс, который вы написали, он также может наследоваться от других классов. И эти классы могут иметь __init__
что идет после этого __init__
основанный на упорядочении классов для разрешения метода.
Без super
вы, вероятно, жестко закодировали бы родительский класс класса, который вы пишете (как в примере). Это будет означать, что вы не будете называть следующий __init__
в MRO, и вы, таким образом, не сможете повторно использовать код в нем.
Если вы пишете свой собственный код для личного использования, вас может не волновать это различие. Но если вы хотите, чтобы другие использовали ваш код, используя super
это одна вещь, которая позволяет большую гибкость для пользователей кода.
Питон 2 против 3
Это работает в Python 2 и 3:
super(Child, self).__init__()
Это работает только в Python 3:
super().__init__()
Он работает без аргументов, перемещаясь вверх по фрейму стека и получая первый аргумент метода (обычно self
для метода экземпляра или cls
для метода класса - но могут быть и другие имена) и поиск класса (например, Child
) в свободных переменных (ищется с именем __class__
в качестве свободной переменной замыкания в методе).
Я предпочитаю демонстрировать кросс-совместимый способ использования super
, но если вы используете только Python 3, вы можете вызвать его без аргументов.
Непрямое движение с прямой совместимостью
Что это тебе дает? Для одиночного наследования примеры из вопроса практически идентичны с точки зрения статического анализа. Однако, используя super
дает вам слой косвенности с прямой совместимостью.
Прямая совместимость очень важна для опытных разработчиков. Вы хотите, чтобы ваш код продолжал работать с минимальными изменениями по мере его изменения. Когда вы просматриваете историю изменений, вы хотите увидеть, что именно изменилось, когда.
Вы можете начать с одного наследования, но если вы решите добавить другой базовый класс, вам нужно будет только изменить строку с базами - если базы изменятся в классе, от которого вы наследуете (скажем, добавлен миксин), вы измените ничего в этом классе. Особенно в Python 2, получение аргументов super
и правильные аргументы метода правильно могут быть трудными. Если вы знаете, что используете 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(SuperChild, self).__init__()
Допустим, вы добавили еще один класс к своему объекту и хотите добавить класс между Foo и Bar (для тестирования или по какой-либо другой причине):
class InjectMe(SomeBaseClass):
def __init__(self):
print('InjectMe.__init__(self) called')
super(InjectMe, self).__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
может наложить ненужные ограничения на пользователей вашего кода.
Я немного поиграл с 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
Для дополнительного чтения я бы порекомендовал следующие ответы:
Разве все это не предполагает, что базовый класс является классом нового стиля?
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)
При звонке super()
чтобы разрешить родительскую версию метода класса, метода экземпляра или статического метода, мы хотим передать текущий класс, область действия которого мы находимся, в качестве первого аргумента, чтобы указать, в какую область родительского элемента мы пытаемся разрешить, и в качестве второго аргумент объекта интереса, чтобы указать, к какому объекту мы пытаемся применить эту область.
Рассмотрим иерархию классов A
, B
, а также C
где каждый класс является родителем следующего за ним, и a
, b
, а также c
соответствующие экземпляры каждого.
super(B, b)
# resolves to the scope of B's parent i.e. A
# and applies that scope to b, as if b was an instance of A
super(C, c)
# resolves to the scope of C's parent i.e. B
# and applies that scope to c
super(B, c)
# resolves to the scope of B's parent i.e. A
# and applies that scope to c
С помощью super
со статическим методом
например, используя super()
изнутри __new__()
метод
class A(object):
def __new__(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
return super(A, cls).__new__(cls, *a, **kw)
Объяснение:
1- хотя это обычно для __new__()
Если в качестве первого параметра взять ссылку на вызывающий класс, он не реализован в Python как метод класса, а скорее как статический метод. То есть ссылка на класс должна быть явно передана в качестве первого аргумента при вызове __new__()
непосредственно:
# if you defined this
class A(object):
def __new__(cls):
pass
# calling this would raise a TypeError due to the missing argument
A.__new__()
# whereas this would be fine
A.__new__(A)
2- при звонке super()
чтобы добраться до родительского класса, мы передаем дочерний класс A
в качестве первого аргумента мы передаем ссылку на интересующий объект, в данном случае это ссылка на класс, которая была передана, когда A.__new__(cls)
назывался. В большинстве случаев это также является ссылкой на дочерний класс. В некоторых ситуациях это может быть не так, например, в случае наследования нескольких поколений.
super(A, cls)
3- так как по общему правилу __new__()
статический метод, super(A, cls).__new__
также будет возвращать статический метод, и в этом случае необходимо явно указать все аргументы, включая ссылку на объект интереса cls
,
super(A, cls).__new__(cls, *a, **kw)
4- делать то же самое без super
class A(object):
def __new__(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
return object.__new__(cls, *a, **kw)
С помощью super
с методом экземпляра
например, используя super()
изнутри __init__()
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
super(A, self).__init__(*a, **kw)
Объяснение:
1- __init__
является методом экземпляра, что означает, что он принимает в качестве первого аргумента ссылку на экземпляр. При вызове непосредственно из экземпляра ссылка передается неявно, то есть вам не нужно указывать ее:
# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...
# you create an instance
a = A()
# you call `__init__()` from that instance and it works
a.__init__()
# you can also call `__init__()` with the class and explicitly pass the instance
A.__init__(a)
2- при звонке super()
в __init__()
мы передаем дочерний класс в качестве первого аргумента, а интересующий объект - в качестве второго аргумента, который в общем случае является ссылкой на экземпляр дочернего класса.
super(A, self)
3- Звонок super(A, self)
возвращает прокси, который разрешит область и применит ее к self
как будто это теперь экземпляр родительского класса. Давайте назовем этот прокси s
, поскольку __init__()
это метод экземпляра вызова s.__init__(...)
будет неявно передать ссылку self
в качестве первого аргумента для родителей __init__()
,
4- сделать то же самое без super
нам нужно передать ссылку на экземпляр явно в версию родителя __init__()
,
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
object.__init__(self, *a, **kw)
С помощью super
с классным методом
class A(object):
@classmethod
def alternate_constructor(cls, *a, **kw):
print "A.alternate_constructor called"
return cls(*a, **kw)
class B(A):
@classmethod
def alternate_constructor(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
print "B.alternate_constructor called"
return super(B, cls).alternate_constructor(*a, **kw)
Объяснение:
1. Метод класса может быть вызван напрямую из класса и в качестве первого параметра принимает ссылку на класс.
# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()
2- при звонке super()
в методе класса, чтобы разрешить его родительскую версию, мы хотим передать текущий дочерний класс в качестве первого аргумента, чтобы указать область родительского объекта, к которому мы пытаемся разрешить, и интересующий объект в качестве второго аргумента, чтобы указать, какой объект мы хотим применить эту область видимости, которая в общем случае является ссылкой на сам дочерний класс или один из его подклассов.
super(B, cls_or_subcls)
3- Звонок super(B, cls)
разрешается в объеме A
и применяет его к cls
, поскольку alternate_constructor()
это метод вызова super(B, cls).alternate_constructor(...)
будет неявно передать ссылку cls
в качестве первого аргумента A
версия alternate_constructor()
super(B, cls).alternate_constructor()
4- сделать то же самое без использования super()
вам нужно будет получить ссылку на несвязанную версию A.alternate_constructor()
(т.е. явная версия функции). Просто сделать это не будет работать:
class B(A):
@classmethod
def alternate_constructor(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
print "B.alternate_constructor called"
return A.alternate_constructor(cls, *a, **kw)
Выше не будет работать, потому что A.alternate_constructor()
метод принимает неявную ссылку на A
в качестве первого аргумента. cls
передаваемый здесь будет вторым аргументом.
class B(A):
@classmethod
def alternate_constructor(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
print "B.alternate_constructor called"
# first we get a reference to the unbound
# `A.alternate_constructor` function
unbound_func = A.alternate_constructor.im_func
# now we call it and pass our own `cls` as its first argument
return unbound_func(cls, *a, **kw)
Коротко о Super()
- У каждого экземпляра Python есть класс, который его создал.
- У каждого класса в Python есть цепочка классов-предков.
- Метод, использующий делегаты super (), работает со следующим предком в цепочке для класса экземпляра.
Пример
Этот небольшой пример охватывает все интересные случаи:
class A:
def m(self):
print('A')
class B(A):
def m(self):
print('B start')
super().m()
print('B end')
class C(A):
def m(self):
print('C start')
super().m()
print('C end')
class D(B, C):
def m(self):
print('D start')
super().m()
print('D end')
Точный порядок вызовов определяется экземпляром, из которого вызывается метод:
>>> a = A()
>>> b = B()
>>> c = C()
>>> d = D()
Например а , нет супер вызова:
>>> a.m()
A
Например, b , цепочка предков
B -> A -> object
:
>>> type(b).__mro__
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
>>> b.m()
B start
A
B end
Например, c , цепочка предков
C -> A -> object
:
>>> type(c).__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
>>> b.m()
C start
A
C end
Например , д , предок цепь более интересная
D -> B -> C -> A -> object
:
>>> type(d).__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
>>> d.m()
D start
B start
C start
A
C end
B end
D end
Больше информации
Ответив на вопрос «Что делает super в Python?», Следующий вопрос - как его эффективно использовать. См. Это пошаговое руководство или это 45-минутное видео.
Множество отличных ответов, но для наглядных учеников: сначала давайте рассмотрим аргументы до супер, а затем без.
Представьте себе, что есть экземпляр jack
создан из класса Jack
, у которого есть цепочка наследования, как показано на рисунке зеленым цветом. Вызов:
super(Jack, jack).method(...)
будет использовать MRO (Порядок разрешения методов) jack
(его дерево наследования в определенном порядке) и начнет поиск с Jack
. Почему можно предоставить родительский класс? Хорошо, если мы начнем поиск с экземпляраjack
, он найдет метод экземпляра, все дело в том, чтобы найти его родительский метод.
Если кто-то не предоставляет аргументы для super, это похоже на то, что первым переданным аргументом является класс self
, а второй переданный аргумент - self
. Они автоматически рассчитываются для вас в Python3.
Однако говорят, что мы не хотим использовать Jack
метод вместо передачи Jack
, мы могли пройти Jen
начать поиск метода снизу вверх Jen
.
Он ищет один слой за раз (ширина, а не глубина), например, если Adam
а также Sue
у обоих есть требуемый метод, тот из Sue
будут найдены первыми.
Если Cain
а также Sue
у обоих был требуемый метод, Cain
метод будет вызываться первым. Это соответствует в коде:
Class Jen(Cain, Sue):
ТОиР слева направо.
В случае множественного наследования обычно требуется вызывать инициализаторы обоих родителей, а не только первого. Вместо того, чтобы всегда использовать базовый класс, super() находит класс, следующий в порядке разрешения методов (MRO), и возвращает текущий объект как экземпляр этого класса. Например:
class Base(object):
def __init__(self):
print("initializing Base")
class ChildA(Base):
def __init__(self):
print("initializing ChildA")
Base.__init__(self)
class ChildB(Base):
def __init__(self):
print("initializing ChildB")
super().__init__()
class Grandchild(ChildA, ChildB):
def __init__(self):
print("initializing Grandchild")
super().__init__()
Grandchild()
приводит к
initializing Grandchild
initializing ChildA
initializing Base
Замена Base.__init__(self)
с участием super().__init__()
приводит к
initializing Grandchild
initializing ChildA
initializing ChildB
initializing Base
по желанию.
Здесь есть отличные ответы, но они не решают, как использовать super()
в случае, когда разные классы в иерархии имеют разные сигнатуры... особенно в случае __init__
ответить на эту часть и уметь эффективно использовать super()
Я бы посоветовал прочитать свой ответ super() и изменить подпись кооперативных методов.
вот как раз решение этого сценария:
- классы верхнего уровня в вашей иерархии должны наследовать от настраиваемого класса, например
SuperObject
:- если классы могут принимать разные аргументы, всегда передавайте все полученные вами аргументы в суперфункцию как аргументы ключевого слова и всегда принимайте
**kwargs
.
class SuperObject:
def __init__(self, **kwargs):
print('SuperObject')
mro = type(self).__mro__
assert mro[-1] is object
if mro[-2] is not SuperObject:
raise TypeError(
'all top-level classes in this hierarchy must inherit from SuperObject',
'the last class in the MRO should be SuperObject',
f'mro={[cls.__name__ for cls in mro]}'
)
# super().__init__ is guaranteed to be object.__init__
init = super().__init__
init()
пример использования:
class A(SuperObject):
def __init__(self, **kwargs):
print("A")
super(A, self).__init__(**kwargs)
class B(SuperObject):
def __init__(self, **kwargs):
print("B")
super(B, self).__init__(**kwargs)
class C(A):
def __init__(self, age, **kwargs):
print("C",f"age={age}")
super(C, self).__init__(age=age, **kwargs)
class D(B):
def __init__(self, name, **kwargs):
print("D", f"name={name}")
super(D, self).__init__(name=name, **kwargs)
class E(C,D):
def __init__(self, name, age, *args, **kwargs):
print( "E", f"name={name}", f"age={age}")
super(E, self).__init__(name=name, age=age, *args, **kwargs)
E(name='python', age=28)
выход:
E name=python age=28
C age=28
A
D name=python
B
SuperObject
Рассмотрим следующий код:
class X():
def __init__(self):
print("X")
class Y(X):
def __init__(self):
# X.__init__(self)
super(Y, self).__init__()
print("Y")
class P(X):
def __init__(self):
super(P, self).__init__()
print("P")
class Q(Y, P):
def __init__(self):
super(Q, self).__init__()
print("Q")
Q()
Если изменить конструктор Y
к X.__init__
, ты получишь:
X
Y
Q
Но используя super(Y, self).__init__()
, ты получишь:
X
P
Y
Q
А также P
или Q
может даже быть задействован из другого файла, о котором вы не знаете, когда пишете X
а также Y
. В общем, вы не знаете, чтоsuper(Child, self)
будет ссылаться на то, когда вы пишете class Y(X)
, даже подпись Y так же проста, как Y(X)
. Вот почему super может быть лучшим выбором.
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
Это довольно легко понять.
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
Хорошо, что происходит сейчас, если вы используете super(Child,self)
?
Когда создается экземпляр Child, его MRO(Порядок разрешения методов) имеет порядок (Child, SomeBaseClass, object) в зависимости от наследования. (предположим, что SomeBaseClass не имеет других родителей, кроме объекта по умолчанию)
Мимоходом Child, self
, super
поиски в MRO self
экземпляр и возвращает прокси-объект рядом с Child, в данном случае это SomeBaseClass, этот объект затем вызывает __init__
метод SomeBaseClass. Другими словами, если это super(SomeBaseClass,self)
прокси-объект, который super
возврат будет object
Для множественного наследования MRO может содержать много классов, поэтому в основном super
позволяет вам решить, где вы хотите начать поиск в MRO.