Понимание Python super() с методами __init__()
Проблема с использованием super()
в Python
Я изучаю использование функции super()
в Python и у меня возникли некоторые вопросы.
Во-первых, для чего именно используется 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()
Когда я запускаю этот код, он вызывает конструктор класса Base
как для ChildA
, так и для ChildB
. Но я не совсем понимаю, в чем разница между использованием Base.__init__(self)
в ChildA
и super(ChildB, self).__init__()
в ChildB
.
Также хотелось бы узнать, какой из подходов является более предпочтительным и в каких случаях стоит использовать каждый из них?
Спасибо за помощь!
3 ответ(ов)
В версии 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
, но поскольку не передаю ему аргументы, получаю ошибку.
Ваша проблема связана с тем, как работает иерархия наследования в Python и что происходит при вызове конструктора класса. Когда вы используете Base = ChildA
, предположительно, ChildA
вызывает своего родительского класса, который также вызывает ChildA
, создавая тем самым бесконечную рекурсию. Важно понимать, что при создании экземпляра класса, конструктор (метод __init__
) его родительского класса также может вызываться.
Для примера, рассмотрим, что у вас есть следующие классы:
class Base:
def __init__(self):
print("Base constructor")
class ChildA(Base):
def __init__(self):
print("ChildA constructor")
super().__init__() # Здесь вызывается конструктор Base
class ChildB(Base):
def __init__(self):
print("ChildB constructor")
super().__init__()
В вашем случае:
Base = ChildA
Base() # Этим вызовом происходит бесконечная рекурсия
Когда вы вызываете Base()
, вы попадаете в ChildA.__init__()
, который вызывает super().__init__()
, тем самым вызывая Base.__init__()
, что опять ведёт к вызову ChildA.__init__()
, и так далее.
Когда вы используете ChildB
, вызов работает как ожидалось:
Base = ChildB
Base() # В этом случае у вас всё работает нормально
Вы можете решить проблему, переопределив метод __init__()
в ChildA
, чтобы он не вызывал super().__init__()
так, как это делается сейчас, если это намеренно не нужно. Также полезно убедиться, что иерархия классов не создает проблем с рекурсией, если они ссылаются друг на друга.
Надеюсь, это поможет вам разобраться с проблемой!
На самом деле, нет. Функция super()
обращается к следующему классу в порядке разрешения методов (MRO, метод разрешения порядка), который можно получить с помощью cls.__mro__
, чтобы вызвать методы. Простой вызов базового __init__
просто вызывает базовый __init__
. В MRO действительно есть только один элемент — базовый класс. Таким образом, вы по сути делаете одно и то же, но чуть более удобно с помощью super()
(особенно это будет полезно, если потом вы столкнетесь с множественным наследованием).
Почему классы в Python наследуют от object?
Разница между старыми и новыми классами в Python?
Когда использовать 'self' вместо '$this'?
Что делают __init__ и self в Python?
Как красиво и "питонично" реализовать несколько конструкторов?