Понимание Python super() с помощью методов __init__()

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

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

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

7 ответов

Решение

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

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

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

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

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

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 отрицательных ответов, ответчик настойчиво предлагал его, пока добрый редактор не решил свою проблему.)

Пояснение: Этот ответ предложил назвать супер так:

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'

Было отмечено, что в 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, чтобы я мог переопределить какой-то другой метод.

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

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

Base = ChildB

Base()

работает как положено

Base = ChildA

Base()

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

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

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

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

Основное отличие состоит в том, что ChildA.__init__ безоговорочно позвонит Base.__init__ в то время как ChildB.__init__ позвоню __init__ в любом классе, случается, ChildB предок в self линия предков (которая может отличаться от ожидаемой).

Если вы добавите 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

затем Base больше не является родителем ChildB за ChildC экземпляров. Сейчас super(ChildB, self) будет указывать на Mixin если self это ChildC пример.

Вы вставили Mixin между ChildB а также Base, И вы можете воспользоваться этим с super()

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

Супер продуманный супер пост и пикон 2015 сопровождающее видео объясняют это довольно хорошо.

Другие вопросы по тегам