Вопрос-Ответ

Understanding Python super() with __init__() methods [duplicate]

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

Почему super() используется?

Есть ли разница между использованием Base.__init__ и super().__init__?

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()
Переведено автоматически
Ответ 1

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

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

Ответ 2

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


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

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

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

В Python 2 мы должны были вызывать super подобным образом с определяющим именем класса и self, но с этого момента мы будем избегать этого, потому что это избыточно, медленнее (из-за поиска имен) и более подробно (поэтому обновите свой Python, если вы еще этого не сделали!):

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

Основное отличие в этом коде заключается в том, что в ChildB вы получаете уровень косвенности в __init__ with super , который использует класс, в котором он определен, для определения следующего класса, который __init__ нужно искать в MRO.

Я иллюстрирую это различие в ответе на канонический вопрос, как использовать 'super' в Python?, который демонстрирует внедрение зависимостей и совместное множественное наследование.

Если в Python не было super

Вот код, который на самом деле близко эквивалентен super (как он реализован на C, за вычетом некоторой проверки и резервного поведения, и переведен на Python):

class ChildB(Base):
def __init__(self):
mro = type(self).mro()
check_next = mro.index(ChildB) + 1 # next 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 со статическими методами невозможно, поскольку у них нет доступа к 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().__init__()

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

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

Теперь запомните, ChildB использует super, ChildA не:

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

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

И UserA не вызывает метод UserDependency:

>>> UserA()
UserA init'ed
ChildA init'
ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

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

Объяснение: Использование self.__class__ в качестве замены имени класса в super() приведет к рекурсии. 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'

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

Ответ 3

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

super().__init__()

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

super(self.__class__, self).__init__()  # DON'T DO THIS!

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

Ответ 4

Super не имеет побочных эффектов

Base = ChildB

Base()

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

Base = ChildA

Base()

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

2023-07-03 10:23 python class