Функция super() в Python, доступ к унаследованным методам
Обеспечивает доступ к оригиналам наследованных методов
Синтаксис:
super(type, object-or-type)
Параметры:
- 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().__getitem__(name) . Это достигается путем реализации собственного метода __getattribute__() для поиска классов в предсказуемом порядке, который поддерживает множественное наследование __mro__ . Функция super() не предназначена для неявных поисков с использованием инструкций или операторов, таких как super()[name] .
Типичные случаи использования super() .
Функция super() используется для обращения к родительским классам в иерархиях с единичным наследованием, чтобы явно не указывать их имена, что упрощает поддержку кода в дальнейшем.
Совсем простой пример для понимания о чем речь:
>>> class A: . def some_method(self): . print('some_method A') . >>> class B(A): . def some_method(self): . print('some_method B') . >>> x = B() >>> x.some_method() # some_method B
Здесь перегружен метод родительского класса. Но что если необходимо только дополнить родительский метод (изменить его поведение), не копируя его полностью? Тут и нужна функция super() :
class A: def some_method(self): print('some_method A') class B(A): def some_method(self): # вызываем метод родительского класса super().some_method() # Добавляем свое поведение print('some_method B') >>> x = B() >>> x.some_method() # some_method A # some_method B
Более практичный пример.
class LoggingDict(dict): def __setitem__(self, key, value): logging.info('Setting %r to %r' % (key, value)) super().__setitem__(key, value)
Класс LoggingDict имеет все те же возможности, что и его родитель dict , плюс расширяет метод __setitem__ для логирования каждого обновлении ключа. После внесения записи в журнал метод использует super() для делегирования работы по фактическому обновлению словаря с помощью пары ключ/значение.
До появления super() нужно было жестко связывать вызов с dict.__setitem__(self, key, value) . Функция super() лучше, т. к. это вычисляемая косвенная ссылка.
Одним из преимуществ косвенности является то, что не нужно указывать класс делегата по имени. Чтобы переключить базовый класс на какое-либо другое сопоставление (вместо dict ), то ссылка super() автоматически переключится на новый родительский класс.
# используем новый базовый # класс SomeOtherMapping class LoggingDict(SomeOtherMapping): def __setitem__(self, key, value): logging.info('Setting %r to %r' % (key, value)) # никаких изменений не требуется super().__setitem__(key, value)
В дополнение к изоляции изменений, у вычисляемой косвенности есть еще одно важное преимущество, которое может быть незнакомо людям, пришедшим из статических языков. Так как косвенность вычисляется во время выполнения, то есть свобода влиять на вычисление, чтобы косвенность указывала на какой-то другой класс.
Расчет зависит как от класса, в котором вызывается super() , так и от «дерева предков» экземпляра. Первый компонент определяется исходным кодом этого класса, в котором вызывается super() . В примере, функция super() вызывается в методе LoggingDict.__setitem__ . Этот компонент фиксирован. Второй компонент, более интересный и носит переменный характер (можно создавать новые подклассы с богатым деревом предков).
Воспользуемся этим поведением для создания упорядоченного словаря журналирования LoggingOD , не изменяя существующие классы:
class LoggingOD(LoggingDict, collections.OrderedDict): pass
И это все, исходный код не изменялся. «Дерево-предок» для нового класса: LoggingOD , LoggingDict , OrderedDict , dict , object . Важным результатом является то, что OrderedDict был вставлен после LoggingDict и перед dict ! Это означает, что вызов super() в LoggingDict.__setitem__ теперь отправляет обновление ключа/значения в OrderedDict , а не в dict .
Задумайтесь как удобно, был создан подкласс LoggingOD , единственная логика которого состоит в том, чтобы правильно составить два существующих класса и управлять «порядком их поиска«.
Порядок поиска или дерево предков — это официально известно как порядок разрешения методов или MRO. Легко просмотреть MRO, распечатав атрибут __mro__ :
>>> pprint(LoggingOD.__mro__) # (, # , # , # , # )
Практические советы.
Функция super() занимается делегированием вызовов методов некоторому классу в дереве предков экземпляра. Чтобы переупорядочиваемые вызовы методов работали, классы должны разрабатываться совместно. При этом возникают три легко решаемые практические задачи:
- Метод, вызываемый super() , должен существовать.
- Вызывающий и вызываемый должны иметь совпадающую подпись аргументов.
- Каждое вхождение метода должно использовать super() .
Рассмотрим стратегии получения аргументов вызывающего объекта, чтобы они соответствовали сигнатуре вызываемого метода. Это немного сложнее, чем традиционные вызовы методов, когда вызываемый объект известен заранее. При использовании super() вызываемый объект неизвестен во время написания класса (т.к. подкласс, написанный позже, может ввести новые классы в «MRO»).
Один из подходов состоит в том, чтобы придерживаться фиксированной подписи, используя позиционные аргументы. Это хорошо работает с такими методами, как __setitem__ , которые имеют фиксированную сигнатуру из двух аргументов, ключа и значения. Этот метод показан в примере выше LoggingDict , где __setitem__ имеет одинаковую подпись как в LoggingDict , так и в dict .
Более гибкий подход состоит в том, чтобы каждый метод в дереве предков был разработан совместно. Другими словами, должен принимать ключевые аргументы и словарь ключевых аргументов **kwargs , удалять любые аргументы, которые ему нужны, и перенаправлять оставшиеся аргументы с помощью **kwargs , в конечном итоге оставляя словарь пустым для последнего вызова в цепочке.
Каждый уровень удаляет ключевые аргументы, которые ему нужны, чтобы окончательный пустой словарь можно было отправить методу, который вообще не ожидает аргументов (например, у метода object.__init__ нет аргументов):
# test.py class Shape: def __init__(self, shapename, **kwds): self.shapename = shapename super().__init__(**kwds) class ColoredShape(Shape): def __init__(self, color, **kwds): self.color = color super().__init__(**kwds) # $ python -i test.py >>> cs = ColoredShape(color='red', shapename='circle')
С сигнатурой разобрались, а как убедиться, что целевой метод существует?
В приведенном выше примере показан простейший случай. Известно, что у любого объекта в Python есть метод object.__init__ , и этот объект всегда является последним классом в цепочке MRO, поэтому любая последовательность вызовов super().__init__ гарантированно заканчивается вызовом метода object.__init__ . Другими словами, есть гарантия того, что цель вызова super().__init__() существует и не вызовет ошибку AttributeError .
Для случаев, когда объект не имеет интересующего метода (например, метода .draw() ), необходимо написать некий класс Root , который гарантированно будет вызываться перед object . Ответственность класса Root состоит в том, чтобы просто съесть вызов метода без переадресации вызова с помощью super() .
Также метод Root.draw() может использовать утверждение, которое будет гарантировать, что он не маскирует какой-либо другой метод .draw() позже в цепочке. Это может произойти, если подкласс ошибочно включает класс, который имеет метод .draw() , но не наследуется от Root :
# test.py class Root: def draw(self): # цепочка делегирования остановится здесь assert not hasattr(super(), 'draw') class Shape(Root): def __init__(self, shapename, **kwds): self.shapename = shapename super().__init__(**kwds) def draw(self): print('Рисование. Установка формы на:', self.shapename) super().draw() class ColoredShape(Shape): def __init__(self, color, **kwds): self.color = color super().__init__(**kwds) def draw(self): print('Рисование. Установка цвета на:', self.color) super().draw() # $ python -i test.py >>> cs = ColoredShape(color='blue', shapename='square') >>> cs.draw() # Рисование. Установка цвета на: blue # Рисование. Установка формы на: square
Если подклассы внедряют другие классы в MRO, эти другие классы также должны наследоваться от Root , чтобы ни один путь поиска вызова .draw() не мог достичь object , не будучи остановленным Root.draw() . Это должно быть четко задокументировано, чтобы тот, кто пишет новые взаимодействующие классы, знал, что это подкласс от Root . Это ограничение мало чем отличается от собственного требования Python, согласно которому все новые исключения должны наследоваться от BaseException .
Методы, показанные выше, гарантируют, что функция super() вызывает метод, о существовании которого известно, и что подпись передаваемых аргументов будет правильной. Осталось только гарантировать непрерывность цепочки делегирования. Этого легко добиться, если классы разрабатываются совместно — просто добавим вызов super() к каждому методу в цепочке.
Примеры получения доступа к унаследованным методам.
Использование функции super() c единичным наследовании:
class Computer(): def __init__(self, computer, ram, ssd): self.computer = computer self.ram = ram self.ssd = ssd # Если создать дочерний класс `Laptop`, то будет доступ # к свойству базового класса благодаря функции super(). class Laptop(Computer): def __init__(self, computer, ram, ssd, model): super().__init__(computer, ram, ssd) self.model = model lenovo = Laptop('lenovo', 2, 512, 'l420') print('This computer is:', lenovo.computer) print('This computer has ram of', lenovo.ram) print('This computer has ssd of', lenovo.ssd) print('This computer has this model:', lenovo.model) # Вывод # This computer is: lenovo # This computer has ram of 2 # This computer has ssd of 512 # This computer has this model: l420
В следующем примере класс Rectangle является суперклассом, а Square является подклассом, поскольку методы Square наследуются от Rectangle , то мы можем вызвать метод __init __() суперкласса ( Rectangle.__ init __() ) из класса Square используя функцию super() . Далее просто пользоваться методами родителя, не написав ни строчки кода. В данном случае квадрат — это частный случай прямоугольника.
class Rectangle: def __init__(self, length, width): self.length = length self.width = width def area(self): return self.length * self.width def perimeter(self): return 2 * self.length + 2 * self.width class Square(Rectangle): def __init__(self, length): # Для квадрата просто нужно передать один параметр length. # При вызове 'super().__init__()' установим атрибуты 'length' и 'width'. super().__init__(length, length) # Класс 'Square' явно не реализует метод 'area()' и # будет использовать его из суперкласса 'Rectangle' sqr = Square(4) print("Area of Square is:", sqr.area()) # Area of Square is: 16 rect = Rectangle(2, 4) print("Area of Rectangle is:", rect.area()) # Area of Rectangle is: 8
И наконец пример работы функции super() при использовании совместного множественного наследования в динамическом окружении:
class A: def __init__(self): print('Initializing: class A') def sub_method(self, b): print('sub_method from class A:', b) class B(A): def __init__(self): print('Initializing: class B') super().__init__() def sub_method(self, b): print('sub_method from class B:', b) super().sub_method(b + 1) class X(B): def __init__(self): print('Initializing: class X') super().__init__() def sub_method(self, b): print('sub_method from class X:', b) super().sub_method(b + 1) class Y(X): def __init__(self): print('Initializing: class Y') # super() с параметрами super(X, self).__init__() def sub_method(self, b): print('sub_method from class Y:', b) super().sub_method(b + 1) x = X() x.sub_method(1) print('Обратите внимание как происходит инициализация') print('классов при указании аргументов в функции super()') y = Y() y.sub_method(5) # Вывод # Initializing: class X # Initializing: class B # Initializing: class A # sub_method from class X: 1 # sub_method from class B: 2 # sub_method from class A: 3 # обратите внимание как происходит инициализация # классов при указании аргументов в функции super() # Initializing: class Y # Initializing: class B # Initializing: class A # sub_method from class Y: 5 # sub_method from class X: 6 # sub_method from class B: 7 # sub_method from class A: 8
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Функция abs(), абсолютное значение числа
- Функция all(), все элементы True
- Функция any(), хотя бы один элемент True
- Функция ascii(), преобразует строку в ASCII
- Функция bin(), число в двоичную строку
- Класс bool(), логическое значение объекта
- Функция breakpoint(), отладчик кода
- Класс bytearray(), преобразует в массив байтов
- Класс bytes(), преобразует в строку байтов
- Функция callable(), проверяет можно ли вызвать объект
- Функция chr(), число в символ Юникода
- Класс classmethod, делает функцию методом класса
- Функция compile() компилирует блок кода Python
- Класс complex(), преобразует в комплексное число
- Функция delattr(), удаляет атрибут объекта
- Класс dict() создает словарь
- Функция dir(), все атрибуты объекта
- Функция divmod(), делит числа с остатком
- Функция enumerate(), счетчик элементов последовательности
- Функция eval(), выполняет строку-выражение с кодом
- Функция exec(), выполняет блок кода
- Функция filter(), фильтрует список по условию
- Класс float(), преобразует в вещественное число
- Функция format(), форматирует значение переменной
- Класс frozenset(), преобразует в неизменяемое множество
- Функция getattr(), значение атрибута по имени
- Функция globals(), переменные глобальной области
- Функция hasattr(), наличие атрибута объекта
- Функция hash(), хэш-значение объекта
- Функция help(), справка по любому объекту
- Функция hex(), число в шестнадцатеричную строку
- Функция id(), идентификатор объекта
- Функция input(), ввод данных с клавиатуры
- Класс int(), преобразует в тип int
- Функция isinstance(), принадлежность экземпляра к классу
- Функция issubclass(), проверяет наследование класса
- Функция iter(), создает итератор
- Функция len(), количество элементов объекта
- Класс list(), преобразовывает в список
- Функция locals(), переменные локальной области
- Функция map(), обработка последовательности без цикла
- Функция max(), максимальное значение элемента
- Класс memoryview(), ссылка на буфер обмена
- Функция min(), минимальное значение элемента
- Функция next(), следующий элемент итератора
- Класс object(), возвращает безликий объект
- Функция oct(), число в восьмеричную строку
- Функция open(), открывает файл на чтение/запись
- Функция ord(), число символа Unicode
- Функция pow(), возводит число в степень
- Функция print(), печатает объект
- Класс property(), метод класса как свойство
- Класс range(), генерирует арифметические последовательности
- Функция repr(), описание объекта
- Функция reversed(), разворачивает последовательность
- Функция round(), округляет число
- Класс set(), создает или преобразовывает в множество
- Функция setattr(), создает атрибут объекта
- Класс slice(), шаблон среза
- Функция sorted(), выполняет сортировку
- Декоратор staticmethod(), метод класса в статический метод
- Класс str(), преобразует объект в строку
- Функция sum(), сумма последовательности
- Функция super(), доступ к унаследованным методам
- Класс tuple(), создает или преобразует в кортеж
- Класс type(), возвращает тип объекта
- Функция vars(), словарь переменных объекта
- Функция zip(), объединить элементы в список кортежей
- Функция __import__(), находит и импортирует модуль
- Функция aiter(), создает асинхронный итератор
- Функция anext(), следующий элемент асинхронного итератора
Зачем нужен super(), если можно и без него?
super() – это встроенная функция языка Python. Она возвращает прокси-объект, который делегирует вызовы методов классу-родителю (или собрату) текущего класса (или класса на выбор, если он указан, как параметр). Основное ее применение и польза – получения доступа из класса наследника к методам класса-родителя в том случае, если наследник переопределил эти методы. источник
16 ноя 2021 в 16:04
Если совсем кратко, то вы забываете учитывать множественное наследование и вылезающие из-за этого проблемы с порядком разрешения методов (mro)
16 ноя 2021 в 16:14
просто посмотри это видео youtube.com/watch?v=61UuKJRl2m0
17 ноя 2021 в 11:08
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
потому что вы записали код, в котором особенности super() никак не используются
Вот какой код вам надо было бы привести:
class B: def c(self): print("B::c") class A(B): def c(self): print("A::c") def __init__(self): A.c(self) # вызывается родной метод c A() class D: def c(self): print("D::c") class C(D): def c(self): print("C::c") def __init__(self): super().c() # вызывается метод c родительского класса D C()
В первом случае через A.c(self) вы вызовете родной метод, а через super().c() _ родительский
У вас же чаще всего будут ситуации, когда и у родителя и у потомка есть один и тот же метод и какой именно вызвать — родной (дочерний) или родительский как раз и будет решаться через вызов super()
Отслеживать
ответ дан 16 ноя 2021 в 16:16
37.1k 4 4 золотых знака 26 26 серебряных знаков 69 69 бронзовых знаков
большое спасибо
16 ноя 2021 в 17:23
Основной смысл в том, чтобы класс-наследник мог вызывать какие-то методы класса-родителя, при этом не указывая название класса-родителя в явном виде. Это удобно на тот случай, если вы захотите поменять название класса-родителя или просто унаследоваться от другого класса. В этом случае вы просто поменяете родителя в заголовке класса и вам не надо будет бегать по коду искать все места использования родителя в потомке, чтобы поменять их. Если у вас используется super() , то совершенно не важно, как называется класс-родитель, к нему доступ будет точно так же через super() и ничего в коде не нужно будет менять.
Отслеживать
ответ дан 16 ноя 2021 в 19:43
67.7k 5 5 золотых знаков 20 20 серебряных знаков 51 51 бронзовый знак
-
Важное на Мете
Похожие
Подписаться на ленту
Лента вопроса
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.10.27.43697
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
super
Возвращает объект-посредник (прокси), делегирующий вызовы методов родителю или собрату класса указанного типа.
super([type[, obj_or_type]])
type — Тип, от которого следует начать поиск объекта-посредника. До -py3.0 атрибут был обязателен.
obj_or_type — Если не указан, возвращается несвязанный объект-посредник. Если передан объект, то вернётся посредник для получения метода такого объекта, для которого isinstance(obj, type) возвращает True. Если передан тип, то вернётся посредник для получения метода такого класса, для которого issubclass(subtype, type) возвращает True.
Используется для доступа к базовым реализациям наследуемых методов, перекрытых в классе-наследнике.
На заметку
Подробное объяснение в видео — Реймонд Хеттинджер: «Super — это супер!».
На заметку
Порядок поиска метода тот же, что и у getattr(), только type пропускается: тип имеет атрибут __mro__, в котором задаётся порядок поиска методов, и который используется как getattr(), так и super() . Атрибут динамический и может изменяться по мере обновления иерархии наследования.
Типичные случаи использования super()
1. В иерархиях с единичным наследованием используется для обращения к родительским классам, чтобы явно не указывать их имена, это упрощает поддержку кода в дальнейшем. Данный тип использования имеет родственников в других языках программирования.
2. Второй вариант используется для поддержки совместного множественного наследования в динамическом окружении. Вариант уникален для Питона и отсутствует в языках статически компилируемых или поддерживающих только единичное наследование. Он делает возможным обращение с ромбовидными иерархиями (см. проблема ромба), при которых несколько базовых классов задают реализацию метода с одним и тем же именем. Правила хорошего проектирования требуют того, чтобы во всех случаях метод имел одну и ту же сигнатуру (в связи с тем, что порядок вызовов определяется во время исполнения, потому как он задаётся при изменении иерархии, и потому что в цепи могут присутствовать собратья, неизвестные до момента исполнения).
На заметку
Использовать super() с двумя аргументами можно, как и любую другую функцию, то есть, не только в методах.
class C(B):
def method(self, arg):
# Python 3
super().method(arg)
# То же, что и для Python 2
super(C, self).method(arg)
На заметку
Функция реализована как часть процесса связывания (объект — атрибут), для случаев, когда атрибут разрешается явно — через точку, таких как super().__getitem__(name). Функционирует это благодаря реализации собственного .__getattribute__(), использующегося для поиска классов в предсказуемом порядке, принимающем во внимание совместное множественное наследование. Поэтому super() не поддерживает неявное разрешение с помощью инструкций или операторов, поэтому, например, super()[name] не сработает.
Без аргументов функция работает только внутри определения класса: необходимые для идентификации класса и доступа к методам его объекта детали заполняет компилятор.
super() – супер класс в 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
