Как Python super делает правильные вещи?

Я использую Python 2.5, поэтому этот вопрос может не относиться к Python 3. Когда вы создаете иерархию алмазных классов с использованием множественного наследования и создаете объект самого производного класса, Python поступает правильно (TM). Он вызывает конструктор для самого производного класса, затем его родительские классы, перечисленные слева направо, затем прародитель. Я знаком с MRO 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 super каким-то образом знает о существовании C в общей иерархии классов. Я знаю, что super возвращает прокси-объект с перегруженным оператором get, но как объект, возвращаемый super в определении инициализации D, сообщает о существовании C объекту, возвращаемому super в определении инициализации B? Сохраняется ли информация о последующих вызовах супериспользования на самом объекте? Если да, то почему не super вместо self.super?

Редактировать: Jekke совершенно справедливо указал, что это не self.super, потому что super является атрибутом класса, а не экземпляром класса. Концептуально это имеет смысл, но на практике super также не является атрибутом класса! Вы можете проверить это в интерпретаторе, создав два класса A и B, где B наследуется от A, и вызвав dir(B). У него нет атрибутов super или __super__.


person Joseph Garvin    schedule 03.03.2009    source источник
comment
Super не является self.super, потому что super является свойством класса, а не экземпляра. (Я не очень понимаю остальную часть вопроса.)   -  person Yes - that Jake.    schedule 03.03.2009
comment
Неважно, может быть; но несколько человек посоветовали мне больше не использовать Python 2.5, поскольку Python 3 представляет так много новых функций / исправляет так много ошибок.   -  person adam    schedule 03.03.2009
comment
Изменить на «как объект, возвращаемый super в определении инициализации D, сообщает о существовании C объекту, возвращаемому super в определении инициализации B»? Я считаю, что это то, о чем вы спрашиваете - это может иметь больше смысла :)   -  person Andy Mikula    schedule 03.03.2009
comment
@ adam: Есть много библиотек, которые еще не перенесены на 3.0. (PyQt, чтобы назвать важный.)   -  person Georg Schölly    schedule 03.03.2009
comment
РЕДАКТИРОВАТЬ: Чтение ссылки Джейкоба Габриэлсона на Super Harmful выявило мой фон Java - super() определенно ведет себя по-другому в Python. Я оставлю остальную часть своего ответа для справки ниже: Публикую как ответ, потому что он слишком велик для комментария: теперь, когда я снова смотрю на него, и имейте в виду, что у меня нет под рукой переводчика , разве вы не ожидаете увидеть: D init B init C init A init в любом случае, потому что C все равно будет вызывать super()? Во всяком случае, я считаю, что такое поведение можно объяснить тем, что все должны знать, что D я   -  person Andy Mikula    schedule 03.03.2009
comment
@adam: вы можете иметь в виду Python 2.6, но не Python 3. 3/214601#214601" title="как вы планируете выполнить миграцию на Python 3"> stackoverflow.com/questions/172306/   -  person jfs    schedule 03.03.2009


Ответы (5)


Я предоставил несколько ссылок ниже, которые отвечают на ваш вопрос более подробно и точнее, чем я когда-либо мог надеяться. Однако я отвечу и на ваш вопрос своими словами, чтобы сэкономить вам время. Поставлю в баллах -

  1. super — это встроенная функция, а не атрибут.
  2. Каждый тип (класс) в Python имеет атрибут __mro__, который хранит порядок разрешения методов для этого конкретного экземпляра.
  3. Каждый вызов super имеет вид super(type[, object-or-type]). Предположим, что второй атрибут является объектом на данный момент.
  4. В начальной точке супервызовов объект имеет тип производного класса (скажем, DC).
  5. super ищет методы, которые соответствуют (в вашем случае __init__) в классах в MRO, после класса, указанного в качестве первого аргумента (в данном случае классов после DC).
  6. Когда соответствующий метод найден (скажем, в классе BC1), он вызывается.
    (Этот метод должен использовать super, поэтому я предполагаю, что это так. См. Python super отлично, но не может (ссылка ниже) Затем этот метод вызывает поиск следующего метода в MRO класса объекта справа от BC1.
  7. Rinse Wash Repeat, пока не будут найдены и вызваны все методы.

Пояснение к вашему примеру

 MRO: D,B,C,A,object  
  1. super(D, self).__init__() называется. isinstance(self, D) => True
  2. Найдите следующий метод в MRO в классах справа от D.

    B.__init__ нашел и позвонил


  1. B.__init__ звонит super(B, self).__init__().

    isinstance(self, B) => False
    isinstance(self, D) => True

  2. Таким образом, MRO тот же, но поиск продолжается справа от B, т. е. объекты C, A и ищутся один за другим. Вызывается следующий найденный __init__.

  3. И так далее.

Объяснение супер
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
На что следует обратить внимание при использовании super
http://fuhm.net/super-harmful/
Алгоритм MRO Python:
http://www.python.org/download/releases/2.3/mro/
документы super:
http://docs.python.org/library/functions.html
Внизу этой страницы есть хороший раздел о super:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

Я надеюсь, что это поможет прояснить ситуацию.

person batbrat    schedule 03.03.2009
comment
в вашем объяснении я не понимаю 3-й пункт isinstance(self, B) => False, тогда зачем продолжать поиск? - person Alcott; 19.09.2011

Измените свой код на это, и я думаю, что это объяснит ситуацию (предположительно 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's Super — отличный инструмент, но вы не можете его использовать.

person Jacob Gabrielson    schedule 03.03.2009
comment
как я и догадался: self всегда относится к классу D. - person Javier; 03.03.2009
comment
Интересно, я бы догадался, что если mro появится при выполнении dir(class), но это не так. Но если сделать dir(class.__class__) то видно! Любая идея, почему несоответствие? class.__mro__ и class.__class__.__mro__ оба работают - person Joseph Garvin; 03.03.2009
comment
Причина в том, что mro определяется метаклассом, а не классом, поэтому он не отображается в каталоге (см. mail.python.org/pipermail/python-dev/2008-March/077604.html). - person Jacob Gabrielson; 03.03.2009

просто предположил:

self во всех четырех методах относятся к одному и тому же объекту, то есть к классу D. поэтому в B.__init__() вызов super(B,self) знает всю алмазную родословную self и должен получить метод из «после» B. в данном случае это класс C.

person Javier    schedule 03.03.2009
comment
self должен относиться к экземпляру содержащего класса, а не к другому классу, не так ли? - person Andy Mikula; 03.03.2009
comment
нет, если вы вызовете self.method(), это будет самая конкретная реализация, независимо от того, насколько вы на высоте. в этом суть полиморфизма - person Javier; 03.03.2009

super() знает полную иерархию классов. Вот что происходит внутри инициализации B:

>>> super(B, self)
<super: <class 'B'>, <D object>>

Это решает центральный вопрос,

как объект, возвращаемый super в определении инициализации D, сообщает о существовании C объекту, возвращаемому super в определении инициализации B?

А именно, в определении инициализации B self является экземпляром D и, таким образом, сообщает о существовании C. Например, C можно найти в type(self).__mro__.

person hrr    schedule 18.12.2012

Ответ Джейкоба показывает, как понять проблему, в то время как batbrat показывает детали, а hrr переходит прямо к делу.

Одна вещь, которую они не охватывают (по крайней мере, не явно) из вашего вопроса, это вот этот момент:

Однако, если вы закомментируете вызов 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()

Немного удивительно видеть, что строка super(B, self).__init__() B на самом деле вызывает 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, который присутствует в классе __mro__ self и имеет такой атрибут.

Таким образом, если вы закомментируете вызов super в функции инициализации B, стек методов остановится на B.__init__ и никогда не достигнет C или A.

Обобщить:

  • Независимо от того, какой класс на него ссылается, self всегда является ссылкой на экземпляр, а его __mro__ и __class__ остаются постоянными.
  • super() находит метод, ищущий классы, которые находятся справа от текущего на __mro__. Поскольку __mro__ остается постоянным, происходит то, что он ищется как список, а не как дерево или график.

Что касается последнего пункта, обратите внимание, что полное название алгоритма MRO — линеаризация суперкласса C3. То есть он сглаживает эту структуру в список. Когда происходят разные вызовы super(), они эффективно повторяют этот список.

person caxcaxcoatl    schedule 09.07.2017