🐛 Типичные ошибки

⚡ Частые ловушки

  • Порядок родителей влияет на выбор метода при совпадающих именах.
  • Миксины не должны хранить состояние и определять __init__.
  • super() идёт по MRO, а не к прямому родителю.
  • Явный вызов Parent.method(self) ломается при изменении иерархии.
  • Композицию и наследование путают: «содержит» ≠ «является».

❌ Ошибка 1: путаница в порядке родителей

class SaveMixin:
    def process(self) -> None:
        print("Saving...")


class PrintMixin:
    def process(self) -> None:
        print("Printing...")


class Document(PrintMixin, SaveMixin):  # ожидали SaveMixin?
    pass


doc = Document()
doc.process()  # Printing...

Почему возникает: Python выбирает метод первого подходящего родителя слева направо. Если порядок не соответствует ожиданиям, вызывается не тот метод.

✅ Исправление

class Document(SaveMixin, PrintMixin):
    pass


doc = Document()
doc.process()  # Saving...

Проверяйте порядок родителей и используйте Class.__mro__, чтобы увидеть реальный порядок.

❌ Ошибка 2: конфликты имён между родителями

class Logger:
    def log(self, message: str) -> None:
        print(f"[LOGGER] {message}")


class FileManager:
    def log(self, filename: str) -> None:
        with open(filename, "w") as file:
            file.write("log entry")


class Worker(Logger, FileManager):
    pass


worker = Worker()
worker.log("data.txt")  # печатает [LOGGER] data.txt, а не пишет в файл

Почему возникает: одинаковые имена методов с разной семантикой. Python не сообщает об ошибке, просто вызывает первый метод в MRO.

✅ Исправление

class Worker(Logger, FileManager):
    def write_log(self, filename: str) -> None:
        FileManager.log(self, filename)

    def print_log(self, message: str) -> None:
        Logger.log(self, message)

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

❌ Ошибка 3: миксин с состоянием

class CounterMixin:
    def __init__(self) -> None:
        self.count = 0  # плохо: миксин хранит состояние

    def increment(self) -> None:
        self.count += 1


class User(CounterMixin):
    def __init__(self, username: str) -> None:
        self.username = username
        # super().__init__() легко забыть

Почему возникает: если наследник не вызовет super().__init__(), атрибут count не появится. А если несколько миксинов определяют __init__, их нужно вызывать в правильном порядке.

✅ Исправление

class CounterMixin:
    def increment(self) -> None:
        self.count += 1


class User(CounterMixin):
    def __init__(self, username: str) -> None:
        self.username = username
        self.count = 0

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

❌ Ошибка 4: неправильное ожидание от super()

class A:
    def action(self) -> None:
        print("A")


class B(A):
    def action(self) -> None:
        print("B")
        super().action()


class C(A):
    def action(self) -> None:
        print("C")
        super().action()


class D(C, B):  # порядок изменён
    def action(self) -> None:
        print("D")
        super().action()


d = D()
d.action()  # D C B A — B вызывается из-за MRO!

Почему возникает: super() идёт по MRO, а не к «ближайшему родителю». В MRO для D(C, B) после C идёт B, поэтому super() внутри C.action() вызывает B.action(), а не сразу A.action().

✅ Исправление

class D(B, C):
    def action(self) -> None:
        print("D")
        super().action()


D().action()  # D B C A

Проверяйте D.__mro__ после изменения порядка родителей.

❌ Ошибка 5: явный вызов родителя вместо super()

class Base:
    def action(self) -> None:
        print("Base")


class Child(Base):
    def action(self) -> None:
        print("Child")
        Base.action(self)  # жёсткая привязка


class GrandChild(Child):
    pass


GrandChild().action()  # Child Base — пока работает

Почему возникает: при добавлении нового родителя между Child и Base вызов всё равно пойдёт в Base, пропустив промежуточный класс.

✅ Исправление

class Child(Base):
    def action(self) -> None:
        print("Child")
        super().action()

super() учитывает актуальный MRO и работает корректно при изменении иерархии.

❌ Ошибка 6: путаница композиции и наследования

class Engine:
    def start(self) -> None:
        print("Engine started")


class Car(Engine):  # машина не является двигателем
    pass


car = Car()
car.start()

Почему возникает: «машина» — это не «двигатель», а «содержит двигатель». Наследование здесь семантически неверно.

✅ Исправление

class Engine:
    def start(self) -> None:
        print("Engine started")


class Car:
    def __init__(self) -> None:
        self.engine = Engine()

    def start(self) -> None:
        self.engine.start()

Используйте наследование для «является», а композицию — для «содержит».

Проверить по документации: подробнее о правилах MRO и типичных ошибках см. Multiple Inheritance и super().