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

2130

Я пытаюсь понять использование 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()
Теги:
class
oop
inheritance
super

7 ответов

1381
Лучший ответ

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

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

  • 13
    Можете ли вы привести пример использования super() с аргументами?
  • 10
    Можете ли вы объяснить super(ChildB, self).__init__() это, что ChildB и self имеют отношение к супер
Показать ещё 2 комментария
423

Я пытаюсь понять 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'
  • 14
    Мне все еще нужно разобраться с этой функцией super() , однако этот ответ, безусловно, является лучшим с точки зрения глубины и деталей. Я также высоко ценю критику в ответе. Это также помогает лучше понять концепцию, выявляя подводные камни в других ответах. Спасибо !
  • 1
    @ Аарон Холл, спасибо за столь подробную информацию. Я думаю, что наставникам должен быть доступен еще один вариант (по крайней мере), чтобы некоторые ответы назывались неуместными или неполными, если они не предоставляют правильную достаточную информацию.
Показать ещё 4 комментария
213

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

  • 38
    Этот ответ говорит о том, что super(self.__class__, self).__init__() не сработает, если вы снова super(self.__class__, self).__init__() подкласс без предоставления нового __init__ . Тогда у вас есть бесконечная рекурсия.
  • 18
    Этот ответ смешной. Если вы собираетесь злоупотреблять super таким образом, вы можете просто жестко закодировать имя базового класса. Это менее неправильно, чем это. Весь смысл первого аргумента супер заключается в том, что это не обязательно тип личности. Пожалуйста, прочитайте "супер считается супер" Реттингером (или посмотрите некоторые из его видео).
Показать ещё 5 комментариев
78

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

Base = ChildB

Base()

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

Base = ChildA

Base()

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

  • 3
    Утверждение «Супер не имеет побочных эффектов» не имеет смысла в этом контексте. Super просто гарантирует, что мы вызываем правильный метод следующего класса в порядке разрешения методов, в то время как другой способ жестко кодирует следующий вызываемый метод, что затрудняет кооперативное множественное наследование.
71

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

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

  • 4
    очень хороший момент. Если вы явно не упомянули: class Base (object): тогда вы получите сообщение об ошибке: «TypeError: должен быть типом, а не classobj»
  • 1
    @andi Я получил эту ошибку на днях, и в конце концов я просто перестал пытаться разобраться. Я просто бездельничал на iPython. Какой ужасный кошмар плохого сообщения об ошибке, если это был код, который мне пришлось отлаживать!
47

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

  • 2
    Понимаю. Не могли бы вы немного рассказать о том, почему лучше использовать super () с множественным наследованием? Для меня база .__ init __ (self) короче (чище). Если бы у меня было два базовых класса, это были бы две из этих строк или две строки super (). Или я неправильно понял, что вы имели в виду под «лучше»?
  • 6
    На самом деле, это будет одна строка super (). Когда у вас множественное наследование, MRO остается плоским. Таким образом, первый вызов super () .__ init__ вызывает init следующего класса, который затем вызывает next и так далее. Вы должны действительно проверить некоторые документы на это.
Показать ещё 4 комментария
24

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

  • 1
    Это. Значение super(ChildB, self) изменяется в зависимости от MRO объекта, на который ссылается self , что не может быть известно до времени выполнения. Другими словами, автор ChildB не имеет возможности узнать, что разрешит super() во всех случаях, если только он не может гарантировать, что ChildB никогда не будет ChildB подклассы.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню