Как «супер» Python делает правильные вещи?
-
03-07-2019 - |
Вопрос
Я использую Python 2.5, поэтому этот вопрос может не относиться к Python 3.Когда вы создаете иерархию ромбовидных классов с использованием множественного наследования и создаете объект наиболее производного класса, Python поступает правильно (TM).Он вызывает конструктор самого производного класса, затем его родительские классы, перечисленные слева направо, а затем родительский класс.Я знаком с Python ТОиР;это не мой вопрос.Мне любопытно, как объект, возвращаемый из super, на самом деле умудряется передавать вызовы super в родительских классах в правильном порядке.Рассмотрим этот пример кода:
#!/usr/bin/python
class A(object):
def __init__(self): print "A init"
class B(A):
def __init__(self):
print "B init"
super(B, self).__init__()
class C(A):
def __init__(self):
print "C init"
super(C, self).__init__()
class D(B, C):
def __init__(self):
print "D init"
super(D, self).__init__()
x = D()
Код делает интуитивную вещь: он печатает:
D init
B init
C init
A init
Однако если вы закомментируете вызов super в функции инициализации B, ни функция инициализации A, ни C не будет вызвана.Это означает, что вызов супер класса B каким-то образом знает о существовании C в общей иерархии классов.Я знаю, что super возвращает прокси-объект с перегруженным оператором get, но как объект, возвращаемый super в определении инициализации D, сообщает о существовании C объекту, возвращаемому super в определении инициализации B?Хранится ли информация, которую последующие вызовы супериспользования хранят в самом объекте?Если да, то почему не super вместо self.super?
Редактировать:Джекке совершенно справедливо заметил, что это не self.super, потому что super — это атрибут класса, а не экземпляр класса.Концептуально это имеет смысл, но на практике супер тоже не является атрибутом класса!Вы можете проверить это в интерпретаторе, создав два класса A и B, где B наследуется от A, и вызвав dir(B)
.У него нет super
или __super__
атрибуты.
Решение
Ниже я предоставил несколько ссылок, которые ответят на ваш вопрос более подробно и точнее, чем я когда-либо мог надеяться.Однако я отвечу на ваш вопрос и своими словами, чтобы сэкономить вам время.Я расставлю это по пунктам -
- super — это встроенная функция, а не атрибут.
- Каждый тип (класс) в Python имеет
__mro__
атрибут, который хранит порядок разрешения метода этого конкретного экземпляра. - Каждый вызов super имеет форму super(type[, object-or-type]).Предположим, что второй атрибут на данный момент является объектом.
- В начальной точке супервызовов объект имеет тип производного класса (скажем, округ Колумбия).
- super ищет подходящие методы (в вашем случае
__init__
) в классах в MRO после класса, указанного в качестве первого аргумента (в данном случае классов после DC). - Когда соответствующий метод найден (скажем, в классе BC1), это называется.
(Этот метод должен использовать Super, поэтому я предполагаю, что он это делает - см. Super's Python изящно, но не может быть использован - ссылка ниже) Этот метод затем вызывает поиск в MRO класса объекта для следующего метода, справа от BC1. - Rinsewash повторяйте до тех пор, пока не будут найдены и вызваны все методы.
Пояснение к вашему примеру
MRO: D,B,C,A,object
super(D, self).__init__()
называется.isinstance(self, D) => TrueИскать следующий метод в МРО в классах справа от Д.
B.__init__
нашел и позвонил
B.__init__
звонкиsuper(B, self).__init__()
.isinstance(self, B) => Ложь
isinstance(self, D) => TrueТаким образом, МРО тот же самый, но поиск продолжается правее В, т.е.C,A,объекты ищутся один за другим.Следующий
__init__
нашел называется.И так далее и тому подобное.
Объяснение супер
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
На что следует обратить внимание при использовании супер
http://fuhm.net/super-harmful/
Алгоритм MRO Pythons:
http://www.python.org/download/releases/2.3/mro/
Документы супер:
http://docs.python.org/library/functions.html
Внизу этой страницы есть хороший раздел о супер:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html
Я надеюсь, что это поможет прояснить ситуацию.
Другие советы
Измените свой код на это, и я думаю, это объяснит ситуацию (предположительно super
смотрит, где, скажем, B
находится в __mro__
?):
class A(object):
def __init__(self):
print "A init"
print self.__class__.__mro__
class B(A):
def __init__(self):
print "B init"
print self.__class__.__mro__
super(B, self).__init__()
class C(A):
def __init__(self):
print "C init"
print self.__class__.__mro__
super(C, self).__init__()
class D(B, C):
def __init__(self):
print "D init"
print self.__class__.__mro__
super(D, self).__init__()
x = D()
Если вы запустите его, вы увидите:
D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
Также стоит проверить Python Super — это здорово, но вы не можете его использовать..
просто предполагаю:
self
все четыре метода относятся к одному и тому же объекту, то есть к классу D
.итак, в B.__init__()
, вызов для super(B,self)
знает всю алмазную родословную self
и он должен получить метод из «после» B
.в данном случае это C
сорт.
super()
знает полный иерархия классов.Вот что происходит внутри инициализации B:
>>> super(B, self)
<super: <class 'B'>, <D object>>
Тем самым решается главный вопрос:
как объект, возвращаемый super в определении инициализации D, сообщает о существовании C объекту, возвращаемому super в определении инициализации B?
А именно, в определении инициализации B, self
является примером D
, и, таким образом, сообщает о существовании C
.Например C
можно найти в type(self).__mro__
.
Ответ Джейкоба показывает, как понять проблему, ответ Бэтбрата показывает детали, а ответ ХР переходит прямо к делу.
Одна вещь, которую они не освещают (по крайней мере, не уточняют) в вашем вопросе, - это следующий момент:
Однако если вы закомментируете вызов super в функции инициализации B, ни функция инициализации A, ни C не будет вызвана.
Чтобы понять это, измените код Джейкоба на печать стека при инициализации A, как показано ниже:
import traceback
class A(object):
def __init__(self):
print "A init"
print self.__class__.__mro__
traceback.print_stack()
class B(A):
def __init__(self):
print "B init"
print self.__class__.__mro__
super(B, self).__init__()
class C(A):
def __init__(self):
print "C init"
print self.__class__.__mro__
super(C, self).__init__()
class D(B, C):
def __init__(self):
print "D init"
print self.__class__.__mro__
super(D, self).__init__()
x = D()
Немного удивительно видеть это B
линия super(B, self).__init__()
на самом деле звонит C.__init__()
, как C
не является базовым классом B
.
D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
File "/tmp/jacobs.py", line 31, in <module>
x = D()
File "/tmp/jacobs.py", line 29, in __init__
super(D, self).__init__()
File "/tmp/jacobs.py", line 17, in __init__
super(B, self).__init__()
File "/tmp/jacobs.py", line 23, in __init__
super(C, self).__init__()
File "/tmp/jacobs.py", line 11, in __init__
traceback.print_stack()
Это происходит потому, что super (B, self)
не является 'вызов версии базового класса B __init__
'.Вместо этого это 'звоню __init__
в первом классе справа от B
который присутствует на self
's __mro__
и у него есть такой атрибут.
Итак, если вы закомментируйте вызов super в функции инициализации B, стек методов остановится на B.__init__
, и никогда не достигнет C
или A
.
Обобщить:
- Независимо от того, какой класс относится к этому,
self
всегда является ссылкой на экземпляр, и его__mro__
и__class__
Остаются неизменными - super() находит метод, просматривающий классы, находящиеся справа от текущего в списке.
__mro__
.Как__mro__
остается постоянным, происходит то, что поиск осуществляется в виде списка, а не дерева или графа.
Что касается последнего пункта, обратите внимание, что полное название алгоритма MRO — Линеаризация суперкласса C3.То есть он сглаживает эту структуру в список.Когда разные super()
вызовы происходят, они эффективно повторяют этот список.