Вопрос: Понимание Python super () с помощью методов __init __ () [duplicate]


На этот вопрос уже есть ответ:

Я пытаюсь понять использование super(), По внешнему виду, оба дочерних класса могут быть созданы, просто отлично.

Мне любопытно узнать о фактической разнице между следующими двумя дочерними классами.

class Base(object):
    def __init__(self):
        print "Base created"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()

ChildA() 
ChildB()

1930


источник


Ответы:


super()позволяет избежать явно ссылаться на базовый класс, что может быть приятным. Но главное преимущество заключается в множественном наследовании, где все виды забавный материал может случиться. См. стандартные документы на супер если вы еще этого не сделали.

Обратите внимание, что синтаксис изменен в Python 3.0 : вы можете просто сказать super().__init__()вместо super(ChildB, self).__init__()что ИМО немного лучше.


1389



Я пытаюсь понять super()

Причина, по которой мы используем superтак что дочерние классы, которые могут использовать совместное множественное наследование, вызывают правильную следующую функцию родительского класса в порядке разрешения метода (MRO).

В Python 3 мы можем назвать это следующим образом:

class ChildB(Base):
    def __init__(self):
        super().__init__() 

В Python 2 мы должны использовать его следующим образом:

        super(ChildB, self).__init__()

Без супер, вы ограничены в своей способности использовать множественное наследование:

        Base.__init__(self) # Avoid this.

Далее я объясню ниже.

«Какая разница в этом коде?»

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()
        # super().__init__() # you can call super like this in Python 3!

Основное различие в этом коде состоит в том, что вы получаете слой косвенности в __init__с super, который использует текущий класс для определения следующего класса __init__искать в MRO.

Я проиллюстрирую это различие в ответе на канонический вопрос, Как использовать «супер» в Python? , который демонстрирует внедрение зависимости а также совместное множественное наследование ,

Если у Python не было super

Вот код, который фактически эквивалентен super(как это реализовано в C, минус некоторое проверочное и резервное поведение и переведено на Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()             # Get the Method Resolution Order.
        check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1

Написано немного больше как родной Python:

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break

Если бы у нас не было superобъект, мы должны будем писать этот ручной код повсюду (или воссоздать его!), чтобы убедиться, что мы вызываем правильный следующий метод в порядке разрешения метода!

Как супер делает это в Python 3 без явного указания того, какой класс и экземпляр из метода, из которого он был вызван?

Он получает фрейм вызывающего стека и находит класс (неявно сохраненный как локальная свободная переменная, __class__, делая вызывающую функцию замыканием над классом) и первый аргумент этой функции, который должен быть экземпляром или классом, который сообщает ему, какой метод разрешения разрешения (MRO) использовать.

Поскольку для этого требуется первый аргумент для MRO, с помощью superсо статическими методами невозможно ,

Критика других ответов:

Функция super () позволяет вам явно не ссылаться на базовый класс, что может быть приятным. , Но главное преимущество заключается в множественном наследовании, где могут случиться всевозможные забавные вещи. См. Стандартные документы на супер, если вы еще этого не сделали.

Это довольно волнительно и не очень много говорит нам, но точка superне следует избегать написания родительского класса. Дело в том, чтобы обеспечить, чтобы вызывался следующий метод в строке в порядке разрешения метода (MRO). Это становится важным при множественном наследовании.

Я объясню здесь.

class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super(ChildB, self).__init__()

И давайте создадим зависимость, которую хотим вызвать после Ребенка:

class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super(UserDependency, self).__init__()

Теперь помните, ChildBиспользует супер, ChildAне:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super(UserA, self).__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super(UserB, self).__init__()

А также UserAне вызывает метод UserDependency:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

Но UserB, потому как ChildBиспользования super, делает !:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>

Критика для другого ответа

Ни при каких обстоятельствах вы не должны делать следующее, на что указывает другой ответ, поскольку вы обязательно получите ошибки, когда вы подклассифицируете ChildB:

        super(self.__class__, self).__init__() # Don't do this. Ever.

(Этот ответ неумный или особенно интересный, но, несмотря на прямую критику в комментариях и более чем 17 downvotes, он продолжал предлагать его, пока добрый редактор не исправил его проблему.)

Объяснение: Этот ответ предложил назвать супер следующим образом:

super(self.__class__, self).__init__()

Это полностью неправильно. superпозволяет найти следующего родителя в MRO (см. первый раздел этого ответа) для дочерних классов. Если вы скажете superмы находимся в методе дочернего экземпляра, тогда он будет искать следующий метод в строке (возможно, этот), что приведет к рекурсии, что, вероятно, приведет к логическому сбою (например, в примере ответчика) или RuntimeErrorкогда глубина рекурсии превышена.

>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'

378



Было отмечено, что в Python 3.0+ вы можете использовать

super().__init__()

чтобы сделать ваш вызов, который является кратким и не требует, чтобы вы ссылались на родительские или имена классов явно, что может быть удобно. Я просто хочу добавить, что для Python 2.7 или ниже можно получить это поведение, нечувствительное к имени, путем написания self.__class__вместо имени класса, т.е.

super(self.__class__, self).__init__()

ОДНАКО, это нарушает призывы к superдля любых классов, которые наследуются от вашего класса, где self.__class__может вернуть дочерний класс. Например:

class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass

Здесь у меня есть класс Square, который является подклассом Rectangle, Скажем, я не хочу писать отдельный конструктор для Squareпотому что конструктор для Rectangleдостаточно хорошо, но по какой-то причине я хочу реализовать квадрат, чтобы я мог переопределить другой метод.

Когда я создаю Squareс помощью mSquare = Square('a', 10,10), Python вызывает конструктор для Rectangleпотому что я не дал Squareего собственный конструктор. Однако в конструкторе для Rectangle, вызов super(self.__class__,self)собирается вернуть суперкласс mSquare, поэтому он вызывает конструктор для Rectangleеще раз. Так происходит бесконечный цикл, как упоминалось @S_C. В этом случае, когда я запускаю super(...).__init__()Я вызываю конструктор для Rectangleно поскольку я не даю ему никаких аргументов, я получу ошибку.


216



Super has no side effects

Base = ChildB

Base()

works as expected

Base = ChildA

Base()

gets into infinite recursion.


74



Just a heads up... with Python 2.7, and I believe ever since super() was introduced in version 2.2, you can only call super() if one of the parents inherit from a class that eventually inherits object (new-style classes).

Personally, as for python 2.7 code, I'm going to continue using BaseClassName.__init__(self, args) until I actually get the advantage of using super().


67



There isn't, really. super() looks at the next class in the MRO (method resolution order, accessed with cls.__mro__) to call the methods. Just calling the base __init__ calls the base __init__. As it happens, the MRO has exactly one item-- the base. So you're really doing the exact same thing, but in a nicer way with super() (particularly if you get into multiple inheritance later).


44



The main difference is that ChildA.__init__ will unconditionally call Base.__init__ whereas ChildB.__init__ will call __init__ in whatever class happens to be ChildB ancestor in self's line of ancestors (which may differ from what you expect).

If you add a ClassC that uses multiple inheritance:

class Mixin(Base):
  def __init__(self):
    print "Mixin stuff"
    super(Mixin, self).__init__()

class ChildC(ChildB, Mixin):  # Mixin is now between ChildB and Base
  pass

ChildC()
help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base

then Base is no longer the parent of ChildB for ChildC instances. Now super(ChildB, self) will point to Mixin if self is a ChildC instance.

You have inserted Mixin in between ChildB and Base. And you can take advantage of it with super()

So if you are designed your classes so that they can be used in a Cooperative Multiple Inheritance scenario, you use super because you don't really know who is going to be the ancestor at runtime.

The super considered super post and pycon 2015 accompanying video explain this pretty well.


22