Понимание 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()
+2029
23 февр. '09 в 0:30
источник поделиться
7 ответов

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

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

+1453
23 февр. '09 в 0:37
источник

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


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

Я пытаюсь понять 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 использует super, 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, do!:

>>> 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'
+445
25 нояб. '14 в 19:00
источник

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

+224
08 окт. '13 в 20:08
источник

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

Base = ChildB

Base()

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

Base = ChildA

Base()

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

+75
27 нояб. '12 в 23:26
источник

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

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

+68
25 мая '12 в 17:52
источник

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

+47
23 февр. '09 в 0:34
источник

Основное отличие состоит в том, что 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, сопровождающий видео объясните это довольно хорошо.

+23
21 сент. '15 в 6:41
источник

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