Перейти к содержанию

232 уроков, 16 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 8 из 20 Средний 30 мин 120 XP

super() и порядок MRO: как Python ищет методы

super() вызывает не «метод родителя», а следующий метод в порядке MRO — порядке, в котором Python ищет атрибуты по иерархии. Разбираем __mro__, расширение методов и кооперативные цепочки вызовов.

Редакция Питоники

В прошлом уроке вы расширяли классы наследованием и трогали super() в __init__. Осталось два вопроса: куда именно смотрит Python, когда ищет метод, и почему super() нельзя понимать как «вызов родителя». Оба ответа спрятаны в механизме MRO (Method Resolution Order) — он определяет и поиск атрибутов, и поведение наследования в иерархиях любой сложности.

Куда смотрит Python, когда вы вызываете метод

Вызов manager.work() запускает поиск атрибута. Сначала Python смотрит в самом объекте, затем в его классе, затем — в родителях. Порядок обхода родителей зафиксирован в атрибуте __mro__ (Method Resolution Order): это кортеж классов, выстроенных так, что каждый класс стоит раньше своих родителей. Линию строит алгоритм C3, и главное его свойство — детерминированность: порядок одинаков при каждом запуске.

метод находится в первом же классе цепочки
class Employee:
    def work(self):
        return "работаю по общему регламенту"

class Manager(Employee):
    def work(self):
        return "управляю проектом"

manager = Manager()
print(manager.work())
print([cls.__name__ for cls in Manager.__mro__])
Вывод
управляю проектом
['Manager', 'Employee', 'object']
  1. Python ищет work в самом объекте manager — среди его атрибутов;
  2. затем в классе Manager — метод найден, поиск закончен;
  3. если бы метода там не было, поиск пошёл бы по __mro__ дальше: Employee, потом object;
  4. если имя не нашлось нигде — AttributeError.
super() расширяет методы вместо копирования
class Account:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def describe(self):
        return f"Счёт {self.owner}: {self.balance} руб."

class SavingsAccount(Account):
    def __init__(self, owner, balance=0, rate=0.05):
        super().__init__(owner, balance)
        self.rate = rate

    def describe(self):
        base = super().describe()
        return base + f" Ставка: {self.rate:.0%}"

account = SavingsAccount("Анна", 10000, 0.07)
print(account.describe())
Вывод
Счёт Анна: 10000 руб. Ставка: 7%

super() — не родитель, а следующий в MRO

Распространённое заблуждение — читать super() как «пойди к родителю». super() без аргументов возвращает прокси к следующему классу в порядке MRO относительно класса, где он написан, — даже если это не родитель, а «брат». super() — не «родительский класс», а следующий класс в порядке MRO: цепочка вызовов доходит до каждого класса и не зацикливается. Проверим на иерархии с двумя родителями.

super() Left ведёт не к Base, а к Right
class Base:
    def tag(self):
        return "Base"

class Left(Base):
    def tag(self):
        return "Left -> " + super().tag()

class Right(Base):
    def tag(self):
        return "Right -> " + super().tag()

class Child(Left, Right):
    def tag(self):
        return "Child -> " + super().tag()

print(Child().tag())
print([cls.__name__ for cls in Child.__mro__])
Вывод
Child -> Left -> Right -> Base
['Child', 'Left', 'Right', 'Base', 'object']
кооперативная цепочка скидок корзины
class Cart:
    def __init__(self, total):
        self.total = total

    def final_total(self):
        print("базовая сумма:", self.total)
        return self.total

class DiscountCart(Cart):
    def final_total(self):
        total = super().final_total()
        total = round(total * 0.9, 2)
        print("скидка 10%:", total)
        return total

class PromoCart(DiscountCart):
    def final_total(self):
        total = super().final_total()
        total -= 100
        print("промокод минус 100:", total)
        return total

order = PromoCart(1000)
print("к оплате:", order.final_total())
Вывод
базовая сумма: 1000
скидка 10%: 900.0
промокод минус 100: 800.0
к оплате: 800.0
Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

class Greeter:
    def greet(self):
        return "привет"

class ShoutingGreeter(Greeter):
    def greet(self):
        return super().greet().upper()

print(ShoutingGreeter().greet())
class Account:
    def __init__(self, owner):
        self.owner = owner

class Card(Account):
    def __init__(self, owner, number):
        super().__init__(owner)
        self.number = number

card = Card("Игорь", "4276")
print(card.owner, card.number)
class A:
    def where(self):
        return "из A"

class B(A):
    def where(self):
        return "из B"

class C(B):
    pass

print(C().where())
Проверь себя
0 / 5

1. Что возвращает super() без аргументов?

2. В каком порядке Python ищет метод при вызове obj.method()?

3. Что напечатает [cls.__name__ for cls in SavingsAccount.__mro__] при SavingsAccount(Account)?

4. Что случится, если потомок объявит __init__ без вызова super().__init__()?

5. Почему кооперативная цепочка super() не зацикливается?

Карточки терминов
Запомнено: 0 / 6
Практика

Создайте класс Employee с полем name и методом describe(), возвращающим строку «Сотрудник: имя». Создайте потомка Developer с дополнительным полем language: его __init__ расширяет родительский через super(), а describe() добавляет к родительской строке «, пишет на язык». Создайте Developer("Пётр", "Python") и выведите describe().

practice.py
Вопросы и ответы по уроку

Что делает super() в Python?

super() возвращает объект-прокси, который передаёт вызов следующему классу в порядке MRO. Чаще всего его используют в __init__ потомка, чтобы инициализировать родительскую часть, и в переопределённых методах, чтобы расширить поведение, а не копировать код родителя.

Урок 7: наследование

Как посмотреть порядок MRO?

У любого класса есть атрибут __mro__ (кортеж классов) и метод mro() (список). Например, print(SavingsAccount.__mro__) покажет линию от самого класса до object — именно в этом порядке Python ищет атрибуты.

Почему super() не всегда вызывает непосредственного родителя?

При множественном наследовании следующий класс в MRO может быть не родителем, а классом-«братом»: в паре Child(Left, Right) super() внутри Left ведёт к Right, и только от него — к Base. Это позволяет каждой классу цепочки внести свою часть работы ровно один раз.

Можно ли вызвать метод родителя напрямую, без super()?

Можно: Employee.describe(self). Но такие вызовы ломают кооперативные цепочки (класс посередине будет пропущен) и хрупки при изменении иерархии. super() всегда следует актуальному порядку MRO, поэтому в новых классах используют его.

Понравился урок? Сошлитесь на него

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.