Понимание 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()
+2332
источник поделиться
7 ответов

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

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

+1694
источник

  Я пытаюсь понять 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.

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

Связанные вопросы


Похожие вопросы

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

super().__init__()

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

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

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

+248
источник

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

Base = ChildB

Base()

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

Base = ChildA

Base()

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

+80
источник

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

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

+71
источник

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

+52
источник

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

супер считается суперпост и pycon 2015, сопровождающий видео объясните это довольно хорошо.

+28
источник

Посмотрите другие вопросы по меткам или Задайте вопрос