Я'пытаюсь понять использование super()
. Судя по всему, оба дочерних класса могут быть созданы, просто отлично.
Мне интересно узнать о фактической разнице между следующими двумя дочерними классами.
class Base(object):
def __init__(self):
print "Base created"
class ChildA(Base):
def __init__(self):
Base.__init__(self)
class ChildB(Base):
def __init__(self):
super(ChildB, self).__init__()
ChildA()
ChildB()
super()
позволяет вам избежать явного обращения к базовому классу, что может быть полезно. Но главное преимущество появляется при множественном наследовании, где могут происходить всевозможные забавные вещи. Посмотрите стандартную документацию по super, если вы еще этого не сделали.
Обратите внимание, что синтаксис изменился в Python 3.0: вы можете просто сказать super().__init__()
вместо super(ChildB, self).__init__()
, что, IMO, гораздо приятнее. Стандартная документация также ссылается на guide to using super(), который достаточно толковый.
Я пытаюсь понять
super()
.
Причина, по которой мы используем super
, заключается в том, что дочерние классы, которые могут использовать кооперативное множественное наследование, будут вызывать правильную функцию следующего родительского класса в порядке разрешения методов (MRO).
В Python 3 мы можем вызвать ее следующим образом:
class ChildB(Base):
def __init__(self):
super().__init__()
В Python 2 мы должны использовать его следующим образом:
super(ChildB, self).__init__()
Без super вы ограничены в возможности использовать множественное наследование:
Base.__init__(self) # Avoid this.
Я объясняю это ниже.
"Какая на самом деле разница в этом коде?:"
class ChildA(Base):
def __init__(self):
Base.__init__(self)
class ChildB(Base):
def __init__(self):
super(ChildB, self).__init__()
# super().__init__() # you can call super like this in Python 3!
Основное различие в этом коде заключается в том, что вы получаете уровень косвенности в __init__
с super
, который использует текущий класс для определения __init__
следующего класса для поиска в MRO.
Я иллюстрирую эту разницу в ответе на канонический вопрос "Как использовать 'super' в Python?", который демонстрирует инъекцию зависимости и кооперативное множественное наследование.
super
.Вот код, который фактически эквивалентен super
(как он реализован в C, за вычетом некоторых проверок и поведения отката, и переведен на Python):
class ChildB(Base):
def __init__(self):
mro = type(self).mro() # Get the Method Resolution Order.
check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
while check_next < len(mro):
next_class = mro[check_next]
if '__init__' in next_class.__dict__:
next_class.__init__(self)
break
check_next += 1
Написан немного более похоже на родной Python:
class ChildB(Base):
def __init__(self):
mro = type(self).mro()
for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
if hasattr(next_class, '__init__'):
next_class.__init__(self)
break
Если бы у нас не было объекта super
, нам пришлось бы везде писать этот ручной код (или создавать его заново!), чтобы убедиться, что мы вызываем правильный следующий метод в порядке разрешения методов!
Как super делает это в Python 3 без явного указания класса и экземпляра метода, из которого он был вызван?
Он получает кадр стека вызывающей функции и находит класс (неявно хранящийся как локальная свободная переменная __class__
, что делает вызывающую функцию закрытием над классом) и первый аргумент этой функции, который должен быть экземпляром или классом, сообщающим ему, какой порядок разрешения методов (MRO) использовать.
Поскольку для MRO требуется этот первый аргумент, использование super
со статическими методами невозможно.
super() позволяет вам избежать явного обращения к базовому классу, что может быть полезно. ... Но основное преимущество появляется при множественном наследовании, где могут происходить всевозможные забавные вещи. Посмотрите стандартную документацию по super, если вы еще этого не сделали.
Это довольно волнообразно и не говорит нам многого, но смысл super
не в том, чтобы избежать написания родительского класса. Смысл в том, чтобы обеспечить вызов следующего по очереди метода в порядке разрешения методов (MRO). This becomes important in multiple inheritance.
I'll explain here.
class Base(object):
def __init__(self):
print("Base init'ed")
class ChildA(Base):
def __init__(self):
print("ChildA init'ed")
Base.__init__(self)
class ChildB(Base):
def __init__(self):
print("ChildB init'ed")
super(ChildB, self).__init__()
And let's create a dependency that we want to be called after the Child:
class UserDependency(Base):
def __init__(self):
print("UserDependency init'ed")
super(UserDependency, self).__init__()
Now remember, ChildB
uses super, ChildA
does not:
class UserA(ChildA, UserDependency):
def __init__(self):
print("UserA init'ed")
super(UserA, self).__init__()
class UserB(ChildB, UserDependency):
def __init__(self):
print("UserB init'ed")
super(UserB, self).__init__()
And UserA
does not call the UserDependency method:
>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>
But UserB
, because ChildB
uses super
, does!:
>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>
In no circumstance should you do the following, which another answer suggests, as you'll definitely get errors when you subclass ChildB:
super(self.__class__, self).__init__() # Don't do this. Ever.
(That answer is not clever or particularly interesting, but in spite of direct criticism in the comments and over 17 downvotes, the answerer persisted in suggesting it until a kind editor fixed his problem.)
Explanation: That answer suggested calling super like this:
super(self.__class__, self).__init__()
This is completely wrong. super
позволяет нам искать следующего родителя в MRO (см. первый раздел этого ответа) для дочерних классов. Если вы скажете super
, что мы находимся в методе дочернего экземпляра, он будет искать следующий по порядку метод (вероятно, этот), что приведет к рекурсии, возможно, вызовет логический сбой (в примере автора ответа так и есть) или RuntimeError
, когда глубина рекурсии будет превышена.
>>> class Polygon(object):
... def __init__(self, id):
... self.id = id
...
>>> class Rectangle(Polygon):
... def __init__(self, id, width, height):
... super(self.__class__, self).__init__(id)
... self.shape = (width, height)
...
>>> class Square(Rectangle):
... pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'
Было замечено, что в Python 3.0+ вы можете использовать
super().__init__()
для вызова, что является лаконичным и не требует явного обращения к именам родительских классов OR, что может быть удобно. Хочу добавить, что для Python 2.7 и младше можно добиться нечувствительного к имени поведения, написав self.__class__
вместо имени класса, т.е.
super(self.__class__, self).__init__()
НО, это нарушает вызов super
для любых классов, которые наследуются от вашего класса, где self.__class__
может вернуть дочерний класс. Например:
class Polygon(object):
def __init__(self, id):
self.id = id
class Rectangle(Polygon):
def __init__(self, id, width, height):
super(self.__class__, self).__init__(id)
self.shape = (width, height)
class Square(Rectangle):
pass
Здесь у меня есть класс Square
, который является подклассом Rectangle
. Допустим, я не хочу писать отдельный конструктор для Square
, потому что конструктор для Rectangle
достаточно хорош, но по какой-то причине я хочу реализовать Square, чтобы я мог повторно реализовать какой-то другой метод.
Когда я создаю Square
с помощью mSquare = Square('a', 10,10)
, Python вызывает конструктор для Rectangle
, потому что я не дал Square
собственного конструктора. Однако в конструкторе для Rectangle
вызов super(self.__class__,self)
вернет суперкласс mSquare
, поэтому он снова вызывает конструктор для Rectangle
. Так происходит бесконечный цикл, о котором говорил @S_C. В этом случае, когда я выполняю super(...).__init__()
, я вызываю конструктор для Rectangle
, но поскольку я не даю ему аргументов, я получу ошибку.
Просто предупреждение... в Python 2.7, и я думаю, с тех пор, как super()
был представлен в версии 2.2, вы можете вызвать super()
, только если один из родителей наследует от класса, который в конечном итоге наследует object
(new-style classes).
Лично я, что касается кода python 2.7, собираюсь продолжать использовать BaseClassName.__init__(self, args)
, пока не получу реальное преимущество от использования super()
.
На самом деле это не так. Для вызова методов super()
обращается к следующему классу в MRO (порядок разрешения методов, доступ к которому осуществляется с помощью cls.__mro__
). Просто вызов базового __init__
вызывает базовый __init__
. Так получилось, что в MRO есть только один элемент - база. Таким образом, вы делаете то же самое, но более приятным способом с помощью super()
(особенно если вы позже займетесь множественным наследованием).
Основное различие заключается в том, что Ребенкаа.__метод init__
безоговорочно называют базой.ChildB инита
.метод initпозвонит
init, которая в Независимо от класса, случается, ChildB
прародителя самообороны
's линии предков
(которые могут отличаться от того, что вы ожидаете).
Если вы добавляете ClassC
, который использует множественное наследование:
class Mixin(Base):
def __init__(self):
print "Mixin stuff"
super(Mixin, self).__init__()
class ChildC(ChildB, Mixin): # Mixin is now between ChildB and Base
pass
ChildC()
help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base
затем "Базовый" - это уже не родитель ChildB
для экземпляров ChildC. Теперь супер(ChildB, самовыдвижение)
указывает на примесь
если я
- это ChildC
экземпляр.
Вы вставили примесь
между ChildB
и база
. И вы можете воспользоваться ею с супер()`
Так что если вы создали свой занятия так, что они могут быть использованы в кооперативе несколько сценариев наследования, вы используете "супер" потому что вы Дон'т действительно знаю, кто собирается быть предком во время выполнения.
В супер считается супер пост и [команда PyCon 2015 видеоклипом][2] это очень хорошо объясняют.