Понимание 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?
В чем разница между __init__ и __call__?
Имеет ли Python "приватные" переменные в классах?
Какова цель конструкторов и деструкторов в PHP?