Как Python "супер" делает правильные вещи? - PullRequest
50 голосов
/ 03 марта 2009

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

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

Ответы [ 5 ]

34 голосов
/ 03 марта 2009

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

15 голосов
/ 03 марта 2009

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

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

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

 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 Питона:
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

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

6 голосов
/ 03 марта 2009

просто угадал:

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

3 голосов
/ 19 декабря 2012

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

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

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

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

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

2 голосов
/ 10 июля 2017

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

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

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

Чтобы понять это, измените код Джейкоба на печать стека в инициализации А, как показано ниже:

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 * __mro__, и имеет такой атрибут .

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

Подведем итог:

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

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

...