Как Python "супер" поступает правильно?
Я запускаю Python 2.5, поэтому этот вопрос может не распространяться на Python 3. Когда вы создаете иерархию классов алмаза с использованием множественного наследования и создаете объект производного класса, Python делает Right Thing (TM). Он вызывает конструктор для производного класса, а затем его родительские классы, перечисленные слева направо, а затем дедушку. Я знаком с Python MRO; это не мой вопрос. Мне любопытно, как объект, возвращенный из супер, действительно управляет, чтобы сообщать вызовам супер в родительских классах правильный порядок. Рассмотрим этот пример кода:
#!/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 init, не вызывается функция A или C init. Это означает, что вызов B в супер так или иначе знает о существовании C в общей иерархии классов. Я знаю, что супер возвращает прокси-объект с перегруженным оператором get, но как объект, возвращаемый супер в определении D init, сообщает существование C объекту, возвращаемому супер в определении B init? Является ли информация о том, что последующие вызовы суперпользователя хранятся на самом объекте? Если да, то почему не супер, а self.super?
Edit: Jekke совершенно справедливо указал, что это не self.super, потому что super является атрибутом класса, а не экземпляром класса. Концептуально это имеет смысл, но на практике супер также не является атрибутом класса! Вы можете проверить это в интерпретаторе, выполнив два класса A и B, где B наследует от A и вызывает dir(B)
. Он не имеет атрибутов super
или __super__
.
Ответы
Ответ 1
Я предоставил несколько ссылок ниже, которые отвечают на ваш вопрос более подробно и точнее, чем я могу когда-либо надеяться. Однако я отвечу на ваш вопрос и в своих собственных словах, чтобы сэкономить вам время. Я поставлю его в пунктах -
- super - встроенная функция, а не атрибут.
- Каждый тип (класс) в Python имеет атрибут
__mro__
, который хранит порядок разрешения метода для этого конкретного экземпляра.
- Каждый вызов super имеет форму super (type [, object-or-type]). Предположим, что второй атрибут является объектом на данный момент.
- В начальной точке супервызов объект относится к типу класса Derived (say DC).
- super ищет методы, которые соответствуют (в вашем случае
__init__
) в классах MRO, после класса, указанного как первый аргумент (в этом случае классы после DC).
- Когда метод сопоставления найден (например, в классе BC1), он вызывается.
(Этот метод должен использовать super, поэтому я предполагаю, что он делает - см. Python super отличный, но не может быть использован - ссылка ниже)
Затем этот метод вызывает поиск в классе объектов MRO для следующего метода справа от BC1.
- Повторите промывку, пока все методы не будут найдены и не будут вызваны.
Объяснение для вашего примера
MRO: D,B,C,A,object
-
super(D, self).__init__()
. isinstance (self, D) = > True
-
Найдите следующий метод в классах MRO справа от D.
B.__init__
найдено и называется
-
B.__init__
вызывает super(B, self).__init__()
.
isinstance (self, B) = > False
isinstance (self, D) = > True
-
Таким образом, MRO - это то же самое, но поиск продолжается справа от B, то есть объект 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
Я надеюсь, что это поможет разобраться.
Ответ 2
Измените свой код на это, и я думаю, что он объяснит вещи (предположительно 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 отличный, но вы не можете его использовать.
Ответ 3
просто гадать:
self
во всех четырех методах относится к одному и тому же объекту, то есть к классу D
.
поэтому в B.__init__()
вызов super(B,self)
знает всю родословную алмаза self
, и он должен получить метод из 'after' B
. в этом случае это класс C
.
Ответ 4
super()
знает иерархию классов полная. Это происходит внутри B init:
>>> super(B, self)
<super: <class 'B'>, <D object>>
Это решает центральный вопрос,
как объект, возвращенный супер в определении D init, связывает существование C с объектом, возвращаемым супер в определении B init?
А именно, в определении B init, self
является экземпляром D
и, таким образом, сообщает о существовании C
. Например C
можно найти в type(self).__mro__
.
Ответ 5
Ответ Джейкоба показывает, как понять проблему, в то время как batbrat показывает детали, и hrr идет прямо к точке.
Одна вещь, которую они не покрывают (по крайней мере, не объяснение) из вашего вопроса, такова:
Однако, если вы закомментируете вызов super в функции B init, не вызывается функция A или C init.
Чтобы понять это, измените код Джейкоба на печать стека на A init, как показано ниже:
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-базовую версию B __init__
". Вместо этого он "вызывает __init__
в первом классе справа от B
, который присутствует на self
__mro__
и имеет такой атрибут.
Итак, если вы закомментируете вызов super в функции B init, стек метода остановится на B.__init__
и никогда не достигнет C
или A
.
Подводя итог:
- Независимо от того, какой класс ссылается на него,
self
всегда ссылается на экземпляр, а его __mro__
и __class__
остаются постоянными
- super() находит метод, смотрящий на классы, которые находятся справа от текущего в
__mro__
. Поскольку константа __mro__
остается постоянной, происходит то, что ее ищут как список, а не как дерево или график.
В этой последней точке обратите внимание, что полное имя алгоритма MRO является линеаризацией суперкласса C3. То есть, он выравнивает эту структуру в списке. Когда происходят различные вызовы super()
, они эффективно повторяют этот список.