Наследование, super() и MRO

⏱ 35 мин · 🧩 заданий: 5

Наследование

Класс-наследник получает все атрибуты и методы родителя и может добавлять новые и переопределять существующие:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "..."

    def describe(self):
        return f"{self.name} говорит: {self.speak()}"

class Dog(Animal):
    def speak(self):                 # переопределение
        return "Гав!"

class Cat(Animal):
    def speak(self):
        return "Мяу"

    def purr(self):                  # новый метод
        return "Мррр"

for pet in [Dog("Шарик"), Cat("Мурка"), Animal("Некто")]:
    print(pet.describe())
print(Cat("Мурка").purr())

describe написан один раз в Animal, но вызывает self.speak() — и для собаки срабатывает собачья версия. Это полиморфизм.

class Animal: pass
class Dog(Animal): pass

d = Dog()
print(isinstance(d, Dog), isinstance(d, Animal), isinstance(d, object))
print(issubclass(Dog, Animal), Dog.__bases__, Dog.__mro__)

Все классы неявно наследуются от object.

super()

super() вызывает реализацию из следующего класса в MRO — обычно это родитель:

class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

    def info(self):
        return f"{self.name} <{self.email}>"

class Admin(User):
    def __init__(self, name, email, permissions):
        super().__init__(name, email)        # родитель инициализирует свои поля
        self.permissions = permissions

    def info(self):
        return super().info() + f" [админ: {', '.join(self.permissions)}]"

a = Admin("Анна", "anna@x.ru", ["delete", "ban"])
print(a.info())

Не забывайте super().init

Если наследник переопределяет __init__ и не вызывает родительский, атрибуты родителя не будут созданы → AttributeError при обращении к ним.

Множественное наследование и MRO

Класс может наследовать от нескольких родителей. Тогда важен порядок разрешения методов (Method Resolution Order):

class A:
    def who(self): return "A"
class B(A):
    def who(self): return "B"
class C(A):
    def who(self): return "C"
class D(B, C):
    pass

print(D().who())
print([cls.__name__ for cls in D.__mro__])
A «ромб»: D наследует A двумя путями / \ B C MRO(D) = D → B → C → A → object \ / D

Python строит MRO алгоритмом C3-линеаризации. Правила:

  1. Наследник всегда раньше родителей.
  2. Порядок родителей из class D(B, C) сохраняется.
  3. Общий предок идёт после всех своих наследников — поэтому A после C, а не сразу после B.

Если такой порядок невозможен, Python откажется создавать класс (TypeError: Cannot create a consistent method resolution order).

Кооперативный super

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

class Base:
    def __init__(self):
        print("Base")

class Left(Base):
    def __init__(self):
        print("Left →")
        super().__init__()

class Right(Base):
    def __init__(self):
        print("Right →")
        super().__init__()

class Child(Left, Right):
    def __init__(self):
        print("Child →")
        super().__init__()

Child()
print([c.__name__ for c in Child.__mro__])

super() внутри Left вызвал Right, хотя Left ничего не знает о Right. Чтобы это работало, все классы цепочки должны вызывать super() и принимать **kwargs, передавая их дальше.

Миксины

Миксин — небольшой класс с одной функцией, который «подмешивают» к другим через множественное наследование. Он не предназначен для самостоятельного использования:

import json

class ReprMixin:
    def __repr__(self):
        fields = ", ".join(f"{k}={v!r}" for k, v in vars(self).items())
        return f"{type(self).__name__}({fields})"

class JsonMixin:
    def to_json(self):
        return json.dumps(vars(self), ensure_ascii=False)

class Order(ReprMixin, JsonMixin):
    def __init__(self, id, total):
        self.id, self.total = id, total

o = Order(1, 990)
print(o, o.to_json())

Миксины широко используются в Django: LoginRequiredMixin, PermissionRequiredMixin в представлениях на классах.

Композиция вместо наследования

Наследование — сильная связь: изменения родителя ломают наследников, глубокие иерархии трудно понимать. Часто лучше композиция: объект содержит другие объекты и делегирует им работу.

class Engine:
    def start(self):
        return "двигатель запущен"

class Radio:
    def play(self):
        return "играет музыка"

class Car:                           # машина ИМЕЕТ двигатель и радио
    def __init__(self):
        self.engine = Engine()
        self.radio = Radio()

    def drive(self):
        return f"{self.engine.start()}, {self.radio.play()}, поехали!"

print(Car().drive())
Отношение Пример Инструмент
«является» (is-a) Собака — животное наследование
«имеет» (has-a) У машины есть двигатель композиция

Принцип

«Предпочитайте композицию наследованию» (Gang of Four). Наследуйте, когда наследник действительно является частным случаем родителя и может заменить его везде (принцип подстановки Лисков).

Итог

  • Наследник получает атрибуты и методы родителя, может добавлять и переопределять.
  • super() вызывает следующий класс в MRO; не забывайте super().__init__(...).
  • MRO строится C3-линеаризацией: наследник раньше родителей, порядок объявления сохраняется; смотреть — Class.__mro__.
  • Миксины добавляют поведение через множественное наследование.
  • Для «имеет» — композиция; для «является» — наследование.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Программа на Python

Есть класс Employee. Создайте наследника Manager: - __init__(self, name, salary, team) вызывает родительский __init__ через super() и сохраняет team (список); - метод annual_bonus() переопределён: у менеджера бонус = родительский бонус + 5000 за каждого человека в команде.

Какие тесты проверяют код (4)
m = Manager("Анна", 200000, ["Боря", "Вера"]); assert m.name == "Анна" and m.team == ["Боря", "Вера"]
m = Manager("Анна", 200000, ["Боря", "Вера"]); assert m.annual_bonus() == 30000
assert isinstance(Manager("x", 1, []), Employee)
assert Employee("x", 1000).annual_bonus() == 100
Задание 2 Один вариант ответа

Какой порядок MRO у класса D?

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
Задание 3 Один вариант ответа

Что выведет код?

class A:
    def hi(self): return "A"
class B(A):
    def hi(self): return "B" + super().hi()
class C(A):
    def hi(self): return "C" + super().hi()
class D(B, C):
    def hi(self): return "D" + super().hi()

print(D().hi())
Задание 4 Программа на Python

Напишите миксин JsonMixin с методом to_json(), который возвращает JSON-строку из атрибутов объекта (vars(self)), с ensure_ascii=False и ключами по алфавиту (sort_keys=True). Затем класс User(JsonMixin) с атрибутами name и age.

Какие тесты проверяют код (1)
assert User("Анна", 28).to_json() == '{"age": 28, "name": "Анна"}'
Задание 5 Один вариант ответа

Когда лучше использовать композицию вместо наследования?

Войдите, чтобы отмечать пройденные уроки.