Как Python "супер" поступает правильно? - программирование
Подтвердить что ты не робот

Как 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__.

4b9b3361

Ответ 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(), они эффективно повторяют этот список.