Вызов конструктора родительского класса python

Обновлено: 02.05.2024

Примеры использования метода .__new__() при создании классов.

При написании классов Python обычно нет необходимости создавать собственную реализацию специального метода .__new__() . В большинстве случаев базовой реализации из встроенного класса объектов достаточно для создания пустого объекта текущего класса.

Тем не менее, есть несколько интересных вариантов использования этого метода.

Общий принцип использования метода .__new__() .

Обычно, создание собственной реализации метода .__new__() необходима только тогда, когда нужно управлять созданием нового экземпляра класса на низком уровне. Теперь, если нужна кастомная реализация этого метода, то следует выполнить несколько шагов:

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

С помощью этих трех кратких шагов можно настроить этап создания экземпляра в процессе создания экземпляра Python.

В этом примере представлена ​​своего рода реализация шаблона .__new__() . Как обычно, .__new__() принимает текущий класс в качестве аргумента, который обычно называется cls .

Обратите внимание, что используются *args и **kwargs , чтобы сделать метод более гибким и удобным в сопровождении, принимая любое количество аргументов. Всегда необходимо определять метод .__new__() с помощью *args и **kwargs , если только нет веской причины следовать другому шаблону.

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

Важно отметить, что сама функция object.__new__() принимает только один аргумент - класс для создания экземпляра. Если вызывать object.__new__() с большим количеством аргументов, то получим исключение TypeError . Однако object.__new__() по-прежнему принимает и передает дополнительные аргументы в конструктор .__init__() , если класс не имеет собственной реализации .__new__() .

Создание подклассов неизменяемых встроенных типов.

Начнем с варианта использования .__new__() , который состоит из подкласса неизменяемого встроенного типа. В качестве примера предположим, что необходимо написать класс Distance как подкласс типа float Python. У класса Distance будет дополнительный атрибут для хранения единицы, которая используется для измерения расстояния.

Первый подход к проблеме с использованием метода .__init__() :

Когда создается подкласс неизменяемого встроенного типа данных, то получаем ошибку. Часть проблемы в том, что значение задается при создании, а менять его при инициализации уже поздно. Кроме того, функция float.__new__() вызывается, как говориться "под капотом", и она не обрабатывает дополнительные аргументы так же, как object.__new__() . Это то, что вызывает ошибку в этом примере.

Чтобы обойти эту проблему, можно инициализировать объект во время создания с помощью .__new__() вместо переопределения в .__init__() .

В этом примере .__new__() выполняет три шага:

  • метод создает новый экземпляр текущего класса cls , вызывая super().__new__() . На этот раз вызов возвращается к float.__new__() , который создает новый экземпляр и инициализирует его, используя значение в качестве аргумента.
  • метод настраивает новый экземпляр, добавляя к нему атрибут .unit .
  • возвращает новый экземпляр.

Теперь класс Distance работает, как и ожидалось, позволяя использовать атрибут экземпляра для хранения единиц измерения. В отличие от значения float , хранящегося в экземпляре Distance атрибут .unit является изменяемым.

Метод .__new__() фабрика случайных объектов.

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

Следующий пример демонстрирует возврат экземпляров случайно выбранных классов:

В этом примере класс Pet предоставляет метод .__new__() , который создает новый экземпляр, случайным образом выбирая класс из списка существующих классов.

Использование класса Pet в качестве фабрики объектов домашних питомцев:

Каждый раз, когда создается экземпляр Pet , то в результате получается случайный объект из другого класса. Такое возможно, т. к. нет ограничений на объект, который может возвращать .__new__() . Использование .__new__() таким образом превращает класс в гибкую и мощную фабрику объектов, не ограниченную экземплярами самого себя.

Наконец, обратите внимание, что метод класса Pet.__init__() никогда не запускается. Это происходит потому, что Pet.__new__() всегда возвращает объекты другого класса, а не самого Pet .

Создание "Singleton" класса.

Иногда нужно реализовать класс, который позволяет создавать только один экземпляр. Этот тип класса широко известен как "Singleton". В этой ситуации удобен метод .__new__() , потому что он может помочь ограничить количество экземпляров, которые может иметь данный класс.

Примечание. Большинство опытных разработчиков утверждают, что не нужно реализовывать шаблон проектирования "Singleton" в Python, если уже нет рабочего класса и нужно добавить функциональность шаблона поверх него. В остальных случаях можно использовать константу уровня модуля, чтобы получить ту же функциональность "Singleton" без необходимости писать относительно сложный класс.

Пример класса Singleton с использованием метода .__new__() , который позволяет создавать только один экземпляр за раз. Для реализации такого поведения, метод .__new__() проверяет наличие предыдущих экземпляров, кэшированных в атрибуте класса:

В этом примере класс Singleton имеет атрибут с именем ._instance , который по умолчанию имеет значение None и работает как кеш. Метод .__new__() проверяет, не существует ли предыдущий экземпляр, проверяя, что условие cls._instance равно None . Если это условие истинно, то блок кода if создает новый экземпляр Singleton и сохраняет его в cls._instance . Наконец, метод возвращает вызывающей стороне новый или существующий экземпляр.

Внимание! В приведенном выше примере Singleton не предоставляет реализацию .__init__() . Если когда-нибудь понадобится такой класс с методом .__init__() , то имейте в виду, что этот метод будет запускаться каждый раз, когда вы вызываете конструктор Singleton() . Такое поведение может вызвать непредсказуемые эффекты инициализации и ошибки.

Надо вызвать метод базового класса из метода, который переопределен в производном классе.

Из конструктора дочернего класса нужно явно вызывать конструктор родительского класса.

Обращение к базовому классу происходит с помощью super()

Нужно явно вызывать конструктор базового класса

Видно, что без явного вызова конструктора класса А не вызывается A.__init__ и не создается поле x класса А.

Вызовем конструктор явно.

Конструктор базового класса стоит вызывать раньше, чем иницилизировать поля класса-наследника, потому что поля наследника могут зависеть (быть сделаны из) полей экземпляра базового класса.

super() или прямое обращение к классу?

Метод класса можно вызвать, используя синтаксис вызова через имя класса:

Все работает. Но при дальнейшем развитии классов могут начаться проблемы:

Видно, что конструктор Base.__init__ вызывается дважды. Иногда это недопустимо (считаем количество созданных экземпляров класса, увеличивая в конструкторе счетчик на 1; выдаем очередное auto id какому-то нашему объекту, например, номер пропуска или паспорта или номер заказа).

То же самое через super():

  • вызов конструктора Base.__init__ происходит только 1 раз.
  • вызваны конструкторы всех базовых классов.
  • порядок вызова конструкторов для классов А и В не определен.

Как это работает?

Для реализации наследования питон ищет вызванный атрибут начиная с первого класса до последнего. Этот список создается слиянием (merge sort) списков базовых классов:

  • дети проверяются раньше родителей.
  • если родителей несколько, то проверяем в том порядке, в котором они перечислены.
  • если подходят несколько классов, то выбираем первого родителя.

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

Не забываем вызывать метод суперкласса

А если где-то не вызван метод суперкласса?

Заметим, что хотя в B.__init__ есть вызов super(), то до вызова B.__init__ не доходит.

  • Вызываем у объекта класса С метод __init__.
  • Ищем его в mro и находим С.__init__. Выполняем его.
  • В этом методе вызов super() - ищем метод __init__ далее по списку от найденного.
  • Находим A.__init__. Выполняем его. В нем нет никаких super() - дальнейший поиск по mro прекращается.

Нет метода в своем базовом классе, есть у родителя моего сиблинга

Определим класс, который пытается вызвать метод, которого нет в базовом классе:

получим, как и ожидалось:

Определим метод spam в классе В. Класс С, наследник А и В, вызывает метод A.spam(), который вызывает B.spam - класс В не связан с классом А.

Для объекта класса С вызвали метод spam(). Ищем его в MRO. Находим A.spam() и вызываем. Далее для super() из A.spam() идем дальше от найденного по списку mro и находим B.spam().

Отметим, что при другом порядке описания родителей class C(B, A) , вызывается метод B.spam() у которого нет super():

Вызываем метод spam для объекта класса С. В С его нет, ищем дальше в В. Находим. Вызваем. Далее super() нет и дальнейший поиск не производится.

Чтобы не было таких сюрпризов при переопределении методов придерживайтесь правил:

  • все методы в иерархии с одинаковым именем имеют одинаковую сигнатуру вызова (количество аргументов и их имена для именованных аргументов).
  • реализуйте метод в самом базовом классе, чтобы цепочка вызовов закончилась хоть каким

Обращение к дедушке

Игнорируем родителя

Если у нас есть 3 одинаковых метода foo(self) в наследуемых классах А, В(А), C(B), и нужно из C.foo() вызвать сразу A.foo() минуя B.foo(), то наши классы неправильно сконструированы (почему нужно игнорировать В? может, нужно было наследовать С от А, а не от В?). Нужен рефакторинг.

Но можно всегда вызвать метод по имени класса:

Метод определен только у дедушки

Если в В такого метода нет, и из C.foo() нужно вызвать A.foo() (или в базовом классе выше по иерархии), вызываем super().foo() и больше не думаем, у какого пра-пра-пра-дедушки реализован этот метод.

Просто воспользуйтесь super() для поиска по mro.

super().super() не работает

Или мы ищем какого-то родителя в mro, или точно указываем из какого класса нужно вызвать метод.

In all other languages I've worked with the super constructor is invoked implicitly. How does one invoke it in Python? I would expect super(self) but this doesn't work.

you should emphasize that an answer that doesn't use the Derived Class name is what you want. e.g. (pseudocode): super().__init__(args. )

you should be accepting Aidan Gomez's answer. It would save us a lot of time, since it has an answer in both python 2 and 3.

@Mike I think there's still value in an answer that lists the Python 2 way, because there's a lot of old Python 2 code floating around out there, and some of the people who wind up at this question probably won't otherwise know how to make sense of it. (And despite it being EOL, many people do still write code in Python 2, either because they don't know better or because some organizational requirement has forced it on them.)

I have changed the accepted answer to @Aiden Gomez's answer. Though Ignacio was correct, @Aidan's was the most appropriate as of today given Python 3's changes to super()

7 Answers 7

You can now choose to sort by Trending, which boosts votes that have happened recently, helping to surface more up-to-date answers.

Trending is based off of the highest score sort and falls back to it if no posts are trending.

In line with the other answers, there are multiple ways to call super class methods (including the constructor), however in Python-3.x the process has been simplified:

Python-3.x

Python-2.x

In python 2.x, you have to call the slightly more verbose version super(, self) , which is equivalent to super() as per the docs.

super() returns a parent-like object in new-style classes:

just of curiosity why does super(B,self) require both B and self to be mentioned? isn't this redundant? shouldn't self contain a reference to B already?

With respect to the documentation of super() , you should be able to write super().__init__() wothout arguments.

With Python 2.x old-style classes it would be this:

@kdbanman: This will work with new-style classes, but one of the reasons to use new-style classes is to not have to do it this way. You can use super and not have to directly name the class you're inheriting from.

One way is to call A's constructor and pass self as an argument, like so:

The advantage of this style is that it's very clear. It call A's initialiser. The downside is that it doesn't handle diamond-shaped inheritance very well, since you may end up calling the shared base class's initialiser twice.

Another way is to use super(), as others have shown. For single-inheritance, it does basically the same thing as letting you call the parent's initialiser.

However, super() is quite a bit more complicated under-the-hood and can sometimes be counter-intuitive in multiple inheritance situations. On the plus side, super() can be used to handle diamond-shaped inheritance. If you want to know the nitty-gritty of what super() does, the best explanation I've found for how super() works is here (though I'm not necessarily endorsing that article's opinions).

Обеспечивает доступ к оригиналам наследованных методов.

Синтаксис:

Параметры:

  • type - необязательно, тип, от которого начинается поиск объекта-посредника
  • object-or-type - необязательно, тип или объект, определяет порядок разрешения метода для поиска

Возвращаемое значение:

  • объект-посредник, делегирующий вызовы методов родителю или собрату класса.

Описание:

Функция super() , возвращает объект объект-посредник, который делегирует вызовы метода родительскому или родственному классу, указанного type типа. Это полезно для доступа к унаследованным методам, которые были переопределены в классе.

object-or-type определяет порядок разрешения метода __mro__ для поиска. Поиск начинается с класса, сразу после указанного типа. Например, если __mro__ - это D -> B -> C -> A -> object , а значение type=B , то super() выполняет поиск объекта C -> A -> object .

  • Если object-or-type не указан, то возвращается несвязанный объект-посредник.
  • Если object-or-type является объектом (экземпляром), то будет получен посредник, для которого isinstance(obj, type) возвращает True .
  • Если object-or-type является типом (классом), то будет получен посредник, для которого issubclass(subtype, type) возвращает True .

Типичные случаи использования super() :

В иерархиях с единичным наследованием используется для обращения к родительским классам, чтобы явно не указывать их имена, это упрощает поддержку кода в дальнейшем. Еще примеры.

Здесь перегружен метод родительского класса. Но что если необходимо немного дополнить родительский метод, не копируя его полностью? Тут и нужна функция super() :

Для поддержки совместного множественного наследования в динамическом окружении. Он делает возможным обращение с ромбовидными иерархиями, при которых несколько базовых классов задают реализацию метода с одним и тем же именем.

Использование функция super() с обоими аргументами точно определяет объекты и делает соответствующие ссылки. Без аргументов функция super() работает только внутри определения класса, а необходимые детали для идентификации класса и доступа к текущему экземпляру для методов заполняет компилятор.

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

Обратите внимание, что super() реализована как часть процесса привязки для явного поиска по точечным атрибутам, таких как super().__getitem__(name) . Это достигается путем реализации собственного метода __getattribute__() для поиска классов в предсказуемом порядке, который поддерживает кооперативное множественное наследование __mro__ . super() и не предназначена для неявных поисков с использованием инструкций или операторов, таких как super()[name] .

Примеры получения доступа к унаследованным методам.

Функция в единичном наследовании:

В следующем примере класс Rectangle является суперклассом, а Square является подклассом, поскольку методы Square наследуются от Rectangle , то мы можем вызвать метод __init __() суперкласса ( Rectangle.__ init __() ) из класса Square используя функцию super() . Далее просто пользоваться методами родителя, не написав ни строчки кода. В данном случае квадрат - это частный случай прямоугольника.

И наконец пример работы функции super() при использовании множественного наследования:

Классы в языке Python поддерживают наследование классов, что позволяет создавать новые классы с расширенным и/или измененным функционалом базового класса. Новый класс, созданный на основе базового класса - называется производный класс (derived class) или просто подкласс.

Подкласс наследует атрибуты и методы из родительского класса. Он так же может переопределять (override) методы родительского класса. Если подкласс не определяет свой конструктор __init__ , то он наследует конструктор родительского класса по умолчанию.

Синтаксис определения производного (дочернего) класса выглядит следующим образом:

Имя BaseClassName должно быть определено в области, содержащей определение производного класса. Вместо имени базового класса допускаются и другие произвольные выражения. Это может быть полезно, например, когда базовый класс определен в другом модуле:

Выполнение определения производного класса DerivedClassName происходит так же, как и для базового класса BaseClassName . Когда объект класса создан, базовый класс BaseClassName запоминается. Это используется для разрешения ссылок на атрибуты. Если запрошенный атрибут не найден в классе DerivedClassName , поиск переходит к поиску в базовом классе BaseClassName . Это правило применяется рекурсивно, если сам базовый класс является производным от какого-либо другого класса.

В создании экземпляров производных классов нет ничего особенного. Выражение a = DerivedClassName() создает новый экземпляр класса. Ссылки на методы разрешаются следующим образом: поиск соответствующего атрибута данных класса осуществляется по цепочке базовых классов, если это необходимо. Ссылка на метод класса будет действительна, если поиск обнаружил функциональный объект.

Производные классы DerivedClassName могут переопределять методы своих базовых классов BaseClassName . Поскольку методы не имеют особых привилегий при вызове других методов того же объекта, метод базового класса, который вызывает другой метод, определенный в том же базовом классе, может в конечном итоге вызвать метод производного класса, который переопределяет его. Для программистов C++ - все методы в Python фактически являются виртуальными.

Переопределяющий метод в производном классе может фактически расширить, а не просто заменить метод базового класса с тем же именем. Существует простой способ вызвать метод базового класса напрямую: просто вызовите BaseClassName.methodname(self, arguments) . Это иногда полезно и для "клиентов". Обратите внимание, что это работает только в том случае, если базовый класс доступен как имя базового класса BaseClassName в глобальной области видимости.

Python имеет две встроенные функции, которые работают с наследованием:

Используйте isinstance() для проверки типа экземпляра класса: isinstance(obj, int) будет истинным True только в том случае, если obj.__class__ равен int или класс является производным от класса int .

Используйте issubclass() для проверки наследования классов: issubclass(bool, int) является истинным, так как bool является подклассом int() . Однако issubclass(float, int) является ложным False , так как float не является подклассом int .

Читайте также: