Какая разница между:
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
и:
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
Я видел, как super довольно часто используется в классах с единственным наследованием. Я понимаю, почему вы бы использовали его при множественном наследовании, но мне неясно, каковы преимущества его использования в такой ситуации.






Преимущества super() при одинарном наследовании минимальны - в большинстве случаев вам не нужно жестко кодировать имя базового класса в каждом методе, который использует его родительские методы.
Однако использовать множественное наследование без super() практически невозможно. Это включает общие идиомы, такие как миксины, интерфейсы, абстрактные классы и т. д. Это распространяется на код, который позже расширяет ваш. Если бы кто-то позже захотел написать класс, расширяющий Child и миксин, его код не работал бы должным образом.
Разве все это не предполагает, что базовый класс является классом нового стиля?
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() и понял, что мы можем изменить порядок вызовов.
Например, у нас есть следующая иерархическая структура:
A
/ \
B C
\ /
D
В этом случае ТОиР of 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
Для дополнительного чтения я бы порекомендовал следующие ответы:
Не понимаю, почему порядок меняется. В первой части я понимаю, что DBCA, потому что D - это первый класс, затем при загрузке self (B, C) в конечном итоге напечатает B, C, затем только A, поскольку B (A), C (A) указали обратно на себя для окончательного часть. Если я следую этому пониманию, то разве вторая часть не должна быть похожа на B-C-A-D? Не могли бы вы мне немного объяснить, пожалуйста.
Моя беда, я не заметил, что каждый экземпляр класса сначала запускался с помощью super (). Тогда, если это так, разве это не должно быть A-B-C-D? Я как-то понимаю, как пришел A-C-B-D, но все еще не мог убедить и все еще немного запутался. Я понимаю, что d = D () называется классом D (B, C) с двумя собственными параметрами, поскольку сначала запускается super (), затем B вызывается вместе с его атрибутами, тогда D не печатается до C, потому что класс D (B, C) содержит 2 собственных параметра, поэтому он должен выполнить второй, который является классом C (A), после выполнения больше не нужно выполнять собственные параметры
тогда он напечатает C, затем напечатает B и, наконец, напечатает D. Я прав?
Второй вариант очень легко понять, если у вас есть первый. Это похоже на стопку. вы помещаете print в стек и выполняете super (), когда он выполняет A, он начинает печатать вещи в этом стеке, поэтому порядок обратный.
Это похоже на рекурсию. Во втором примере он сначала вызывает все классы, помещает их в очередь (или стек), потому что сначала вызывается super (). Затем, когда он переходит к базовому классу, он выполняет метод печати базового класса и переходит к следующему в очереди (или в стеке, как сказал @grantsun). И в первом примере сначала вызывается print () из D, поэтому он сначала печатает «Я из D», и только потом переходит к следующему классу, где снова сначала видит print () и только потом вызывает super ()
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
Это довольно легко понять.
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
Хорошо, что теперь произойдет, если вы воспользуетесь super(Child,self)?
Когда создается дочерний экземпляр, его MRO (порядок разрешения методов) находится в порядке (Child, SomeBaseClass, object) на основе наследования. (предположим, что SomeBaseClass не имеет других родителей, кроме объекта по умолчанию)
Передавая Child, self, super выполняет поиск в MRO экземпляра self и возвращает прокси-объект, следующий за Child, в данном случае это SomeBaseClass, этот объект затем вызывает метод __init__ SomeBaseClass. Другими словами, если это super(SomeBaseClass,self), то прокси-объект, который возвращает super, будет object.
Для множественного наследования MRO может содержать много классов, поэтому в основном super позволяет вам решить, где вы хотите начать поиск в MRO.
здесь есть отличные ответы, но они не касаются того, как использовать super() в случае, когда разные классы в иерархии имеют разные подписи ... особенно в случае __init__
чтобы ответить на эту часть и иметь возможность эффективно использовать super(), я предлагаю прочитать свой ответ super () и изменение подписи кооперативных методов.
вот как раз решение этого сценария:
- the top-level classes in your hierarchy must inherit from a custom class like
SuperObject:- if classes can take differing arguments, always pass all arguments you received on to the super function as keyword arguments, and, always accept
**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
Много отличных ответов, но для наглядных учеников:
Сначала давайте рассмотрим аргументы до супер, а затем без.

Представьте, что существует экземпляр 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):
MRO слева направо.
Рассмотрим следующий код:
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 может быть лучшим выбором.
В случае множественного наследования обычно требуется вызывать инициализаторы обоих родителей, а не только первого. Вместо того, чтобы всегда использовать базовый класс, 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
по желанию.
Этот небольшой пример охватывает все интересные случаи:
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 -> 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, цепочка предков более интересна 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-минутное видео.
Можете ли вы привести пример того, что вы имеете в виду, говоря «это не будет работать должным образом»?