Понимание методов Python super() и __init__()

Я'пытаюсь понять использование 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()
Решение

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

Обратите внимание, что синтаксис изменился в Python 3.0: вы можете просто сказать super().__init__() вместо super(ChildB, self).__init__(), что, IMO, гораздо приятнее. Стандартная документация также ссылается на guide to using super(), который достаточно толковый.

Комментарии (5)

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

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

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

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

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

super(ChildB, self).__init__()

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

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.

Я иллюстрирую эту разницу в ответе на канонический вопрос "Как использовать 'super' в 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, нам пришлось бы везде писать этот ручной код (или создавать его заново!), чтобы убедиться, что мы вызываем правильный следующий метод в порядке разрешения методов!

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

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

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

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

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

Это довольно волнообразно и не говорит нам многого, но смысл super не в том, чтобы избежать написания родительского класса. Смысл в том, чтобы обеспечить вызов следующего по очереди метода в порядке разрешения методов (MRO). This becomes important in multiple inheritance.

I'll explain here.

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__()

And let's create a dependency that we want to be called after the Child:

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

Now remember, ChildB uses super, ChildA does not:

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__()

And UserA does not call the UserDependency method:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed

But UserB, because ChildB uses super, does!:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed

Criticism for another answer

In no circumstance should you do the following, which another answer suggests, as you'll definitely get errors when you subclass ChildB:

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

(That answer is not clever or particularly interesting, but in spite of direct criticism in the comments and over 17 downvotes, the answerer persisted in suggesting it until a kind editor fixed his problem.)

Explanation: That answer suggested calling super like this:

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

This is completely wrong. 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 "", line 1, in 
  File "", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'
Комментарии (6)

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

super().__init__()

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

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

Комментарии (7)

Супер не имеет побочных эффектов

Base = ChildB

Base()

работает, как ожидалось

Base = ChildA

Base()

попадает в бесконечную рекурсию.

Комментарии (1)

Просто предупреждение... в Python 2.7, и я думаю, с тех пор, как super() был представлен в версии 2.2, вы можете вызвать super(), только если один из родителей наследует от класса, который в конечном итоге наследует object (new-style classes).

Лично я, что касается кода python 2.7, собираюсь продолжать использовать BaseClassName.__init__(self, args), пока не получу реальное преимущество от использования super().

Комментарии (2)

На самом деле это не так. Для вызова методов super() обращается к следующему классу в MRO (порядок разрешения методов, доступ к которому осуществляется с помощью cls.__mro__). Просто вызов базового __init__ вызывает базовый __init__. Так получилось, что в MRO есть только один элемент - база. Таким образом, вы делаете то же самое, но более приятным способом с помощью super() (особенно если вы позже займетесь множественным наследованием).

Комментарии (6)

Основное различие заключается в том, что Ребенкаа.__метод init__ безоговорочно называют базой.ChildB инита.метод initпозвонитinit, которая в Независимо от класса, случается, ChildB прародителя самообороны's линии предков (которые могут отличаться от того, что вы ожидаете).

Если вы добавляете ClassC, который использует множественное наследование:

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

затем "Базовый" - это уже не родитель ChildB для экземпляров ChildC. Теперь супер(ChildB, самовыдвижение) указывает на примесь если я - это ChildC экземпляр.

Вы вставили примесь между ChildB и база. И вы можете воспользоваться ею с супер()`

Так что если вы создали свой занятия так, что они могут быть использованы в кооперативе несколько сценариев наследования, вы используете "супер" потому что вы Дон'т действительно знаю, кто собирается быть предком во время выполнения.

В супер считается супер пост и [команда PyCon 2015 видеоклипом][2] это очень хорошо объясняют.

Комментарии (1)