32

Понимание Python super() с методами __init__()

19

Проблема с использованием 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 ответ(ов)

2

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

0

Ваша проблема связана с тем, как работает иерархия наследования в 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__() так, как это делается сейчас, если это намеренно не нужно. Также полезно убедиться, что иерархия классов не создает проблем с рекурсией, если они ссылаются друг на друга.

Надеюсь, это поможет вам разобраться с проблемой!

0

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь