Python — о множественном наследовании и функции super() простыми словами
Python позволяет указать для класса несколько родителей. Это называется множественным наследованием.
Например, мы хотим добавить какие-то общие свойства нескольким разным классам. Добавлять эти свойства, через класс-наследник для каждого из классов явно некрасиво, нарушает принцип DRY.
Если речь о чем-то простом, то это можно сделать через декоратор. Но если это что-то более развесистое и прикладное, то напрашивается оформить это как объект, и добавить к нужным классам как mixin. В Python нет специального способа добавлять mixin, это осуществляется через множественное наследование.
Загадка
Platypus.__init__() Mammal.__init__() Bird.__init__() Vertebrate.__init__()
Если хоть один наследник нарушает принципы кооперативного наследования (не вызывает super() ), то метод родителя вообще не будет вызван, хотя вроде бы мы имеем явный вызов этого родителя из другого наследника.
Например, давайте закомментарим вызов super() в классе Bird (строка 8). Вывод изменится следующим образом:
Platypus.__init__() Mammal.__init__() Bird.__init__()
Причина в том, что из Mammal.__init__ вызывается следующий по MRO класс ( Bird ), а вовсе не родитель Mammal ( Vertebrate ). Родителя ранее вызывал Bird , но мы убрали этот вызов.
В коде ниже я добавил аргументы в __init__ , чтобы проиллюстрировать сказанное выше выдаваемой ошибкой.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20class Vertebrate: def __init__(self): print('Vertebrate.__init__()') class Bird(Vertebrate): def __init__(self, beak_length): print('Bird.__init__()') super().__init__() class Mammal(Vertebrate): def __init__(self, hair_length): print('Mammal.__init__()') super().__init__() class Platypus(Mammal, Bird): def __init__(self): print('Platypus.__init__()') super().__init__(1) duckbill = Platypus()Platypus.__init__() Mammal.__init__() . File "animal_class_tree_arguments.py", line 13, in __init__ super().__init__() TypeError: __init__() missing 1 required positional argument: 'beak_length'Ошибка показывает, что в Mammal код super().__init__() пытается вызвать Bird.__init__ .
Примечание - особенности работы super()
Одним из ограничений super() является то, что не получится выполнить операции (binary operations, subscriptions и т.д.) над возвращенным объектом, даже если эти операции реализованы в родителе вызывающего класса с помощью “магических методов”.
Если выполнить операцию над экземпляром класса, то Python найдет нужный для выполнения операции “магический метод” в родителе (в примере ниже - __getitem__ для индексирования с помощью оператора [] ).
Но если попытаться выполнить операцию над объектом, возвращаемым super() , получим ошибку:
class Parent: def __getitem__(self, idx): return 0 class Child(Parent): def index_super(self, idx): return super()[idx] kid = Child() print(f'kid[0]: kid[0]>') print(f'kid.index_super(0): kid.index_super(0)>')kid[0]: 0 . TypeError: 'super' object is not subscriptablesuper() – супер класс в Python
super() – это встроенная функция языка Python. Она возвращает прокси-объект, который делегирует вызовы методов классу-родителю (или собрату) текущего класса (или класса на выбор, если он указан, как параметр).
Основное ее применение и польза – получения доступа из класса наследника к методам класса-родителя в том случае, если наследник переопределил эти методы.
Что такое прокси-объект? Прокси, по-русски, это заместитель. То есть это объект, который по смыслу должен вести себя почти так же, как замещенный объект. Как правило он перенаправляет вызовы своих методов к другому объекту.
Давайте рассмотрим пример наследования. Есть какой-то товар в классе Base с базовой ценой в 10 единиц. Нам понадобилось сделать распродажу и скинуть цену на 20%. Хардкодить – это непрофессионально и негибко:
class Base: def price(self): return 10 class Discount(Base): def price(self): return 8Гораздо лучше было бы получить цену из родительского класса Base и умножить ее на коэффициент 0.8, что даст 20% скидку. Однако, если мы вызовем self.price() в методе price() мы создадим бесконечную рекурсию, так как это и есть один и тот же метод класса Discount! Тут же нужен метод Base.price() . Тогда его и вызовем по имени класса:
class Discount(Base): def price(self): return Base.price(self) * 0.8Здесь, надо не забыть указать self при вызове первым параметром явно, чтобы метод был привязан к текущему объекту. Это будет работать, но этот код не лишен изъянов, потому что необходимо явно указывать имя предка. Представьте, если иерархия классов начнет разрастаться? Например, нам нужно будет вставить между этими классами еще один класс, тогда придется редактировать имя класса-родителя в методах Discount:
class Base: def price(self): return 10 class InterFoo(Base): def price(self): return Base.price(self) * 1.1 class Discount(InterFoo): #Тут на помощь приходит super() ! Супер он не потому что, подобно Супермэну, помогает всем людям, а потому что обращается к атрибутам классов стоящих над ним в порядке наследования (кто учил матан, вспомнят понятие супремум).
Будучи вызванным без параметров внутри какого-либо класса, super() вернет прокси-объект, методы которого будут искаться только в классах, стоящих ранее, чем он, в порядке MRO. То есть, это будет как будто бы тот же самый объект, но он будет игнорировать все определения из текущего класса, обращаясь только к родительским:
class Base: def price(self): return 10 class InterFoo(Base): def price(self): return super().price() * 1.1 class Discount(InterFoo): def price(self): return super().price() * 0.8
Для Discount порядок MRO: Discount - InterFoo - Base - object . Вызов super().method() внутри класса Discount будет игнорировать Discount.method() , а будет искать method в InterFoo, затем, если не найдет, то в Base и object.
Когда нельзя забыть super?
Очень часто super вызывается в методе __init__. Метод инициализации класса __init__, как правило задает какие-либо атрибуты экземпляра класса, и если в дочернем классе мы забудем его вызвать, то класс окажется недоинициализированным: при попытке доступа к родительским атрибутам будет ошибка:
class A: def __init__(self): self.x = 10 class B(A): def __init__(self): self.y = self.x + 5 # print(B().y) # ошибка! AttributeError: 'B' object has no attribute 'x' # правильно: class B(A): def __init__(self): super().__init__() #Параметры super
Функция может принимать 2 параметра. super([type [, object]]) . Первый аргумент – это тип, к предкам которого мы хотим обратиться. А второй аргумент – это объект, к которому надо привязаться. Сейчас оба аргумента необязательные. В прошлых версиях Python приходилось их указывать явно:
class A: def __init__(self, x): self.x = x class B(A): def __init__(self, x): super(B, self).__init__(x) # теперь это тоже самое: super().__init__(x)Теперь Python достаточно умен, чтобы самостоятельно подставить в аргументы текущий класс и self для привязки. Но старая форма тоже осталась для особых случаев. Она нужна, если вы используете super() вне класса или хотите явно указать с какого класса хотите начать поиск методов.
Действительно, super() может быть использована вне класса. Пример:
d = Discount() print(super(Discount, d).price())В этом случае объект, полученный из super(), будет вести себя как класс InterFoo (родитель Discount), хотя привязан он к переменной d, которая является экземпляром класса Discount.
Это редко используется, но, вероятно, кому-то будет интересно узнать, что функция super(cls), вызванная только с одним параметром, вернет непривязанный к экземпляру объект. У него нельзя вызывать методы и обращаться к атрибутам. Привязать его можно будет так:
super_d = super(Discount) d = Discount() binded_d = super_d.__get__(d, Discount) # привязка print(binded_d.price()) # 11.0Множественное наследование
В случае множественного наследования super() необязательно указывает на родителя текущего класса, а может указывать и на собрата. Все зависит от структуры наследования и начальной точки вызова метода. Общий принцип остается: поиск начинается с предыдущего класса в списке MRO. Давайте рассмотрим пример ромбовидного наследования. Каждый класс ниже в методе method печатает свое имя. Плюс все, кроме первого, вызывают свой super().method() :
class O: def method(self): print('I am O') class A(O): def method(self): super().method() print('I am A') class B(O): def method(self): super().method() print('I am B') class C(A, B): def method(self): super().method() print('I am C')Если вызвать метод C().method() , то в терминале появится такая распечатка:
# C().method() I am O I am B I am A I am CВидно, что каждый метод вызывается ровно один раз и ровно в порядке MRO. C вызывает родителя A, а A вызывает своего брата B, а B вызывает их общего родителя O. Но! Стоит нам вызвать A().method() , он уже не будет вызывать B().method() , так как класса B нет среди его родителей, он брат, а родитель у класс А только один – это O. А о братьях он и знать не хочет:
# A().method() I am O I am AТаким образом, вызов super() сам автоматически догадывается, к кому обращаться: к родителю или к брату. Все зависит от иерархии класса и начальной точки вызова. Эта фишки снимают с программиста головную боль, связанную с заботой о поддержании цепочки вызовов в иерархии классов.
Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway
Объясните как работает метод super,а так-же конструкция super().__init__() в python? [закрыт]
Хотите улучшить этот вопрос? Переформулируйте вопрос так, чтобы он был сосредоточен только на одной проблеме.
Закрыт 4 года назад .
Объясните как работает метод super , а так-же конструкция super().__init__() в python? super часто используется в классах. Желательно объяснить как можно проще и на простых примерах.
Отслеживать
66.4k 6 6 золотых знаков 51 51 серебряный знак 112 112 бронзовых знаков
задан 1 июн 2019 в 12:31
366 1 1 золотой знак 3 3 серебряных знака 13 13 бронзовых знаков
1 июн 2019 в 13:05Отличный ответ но гуглом я умею пользоваться.Читаю и разбираюсь но на данный момент понять не могу, сюда написал затем,что-бы может у кого нибудь получилось объяснить.
1 июн 2019 в 13:35
Raymond Hettinger - Super considered super! youtu.be/EiOglTERPEo
1 июн 2019 в 14:09
Нашёл такой же ролик только с русским переводом youtube.com/watch?v=61UuKJRl2m0
1 июн 2019 в 14:121 ответ 1
Сортировка: Сброс на вариант по умолчанию
super() - это функция, которая обращается к классу, от которого наследуется текущий.
Сначала немного кода:
class A: def some_method(self): print('Spam, eggs!!1') class B(A): def some_method(self): print('Hello, World!') x = B() x.some_method()Hello, World!Как Вы можете заметить, мы перегрузили метод родительского класса. Но что, если нам потребуется всего лишь немного дополнить его? Как нам дополнить родительский метод, не копируя его полностью и не изобретая велосипедов? Тут нам и нужен super() :
class A: def some_method(self): print('Spam, eggs!!1') class B(A): def some_method(self): super().some_method() print('Hello, World!') x = B() x.some_method()Spam, eggs!!1 Hello, World!С помощью super().some_method() мы вызвали родительский метод, а после дополнили свой. Именно для этого чаще всего используется эта функция.
__init__() - это метод инициализации класса, следовательно super().__init__() вызывает метод инициализации из родительского класса. Например, чтобы дополнить его.
Что такое super в python
Функция super() в Python: вызов методов родительского класса
19 марта 2023
Оценки статьи
Еще никто не оценил статьюФункция super() в Python используется для вызова методов родительского класса. В этой статье мы рассмотрим, что такое super() , как он работает, и как его можно использовать.
Что такое super() в Python?
super() - это функция, которая позволяет вызывать методы родительского класса в дочернем классе. Она используется, когда нужно вызвать методы родительского класса в дочернем классе, чтобы избежать дублирования кода и улучшить его читаемость.
Функция super() можно использовать для вызова методов как в методах, так и в конструкторах дочернего класса.
Как работает функция super() в Python?
Когда мы создаем дочерний класс, он наследует все атрибуты и методы родительского класса. Для того, чтобы вызвать метод родительского класса, мы можем использовать функцию super() .
При вызове super() Python ищет родительский класс в иерархии наследования, начиная с текущего класса и переходя к родительским классам в порядке, заданном в списке наследования. После нахождения родительского класса, super() вызывает его методы и передает все аргументы, которые были переданы методу дочернего класса.
Пример использования super() в Python
Рассмотрим пример, который показывает, как использовать super() для вызова метода родительского класса.
class Animal: def __init__(self, name): self.name = name def make_sound(self): print("The animal makes a sound") class Dog(Animal): def __init__(self, name): super().__init__(name) def make_sound(self): super().make_sound() print("The dog barks") my_dog = Dog("Buddy") my_dog.make_sound()В этом примере у нас есть два класса: Animal и Dog . Класс Dog наследует от Animal .
Мы переопределяем метод make_sound() в классе Dog , но вместо того, чтобы полностью переписывать метод, мы вызываем метод make_sound родительского класса с помощью super() , а затем добавляем к нему свой код.
Результат выполнения этого кода будет:
>>> The animal makes a sound >>> The dog barksВ этом примере мы вызываем метод make_sound() родительского класса с помощью super() , а затем добавляем к нему свой код, чтобы сделать ту же самую вещь, но с дополнительным функционалом.

