Вопрос

Я использую 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__ атрибуты.

Это было полезно?

Решение

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

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

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

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

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


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

    isinstance(self, B) => Ложь
    isinstance(self, D) => True

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

  3. И так далее и тому подобное.

Объяснение супер
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() вызовы происходят, они эффективно повторяют этот список.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top