⚖️ Старый и современный подход

⚡ Различия в двух словах

Старый: вызов родителя по имени Parent.__init__(self) и проверка типа через type(obj) == Class.

Новый: super().__init__(...), isinstance() и аннотации типов.

Вывод: super() и isinstance() делают код гибче и устойчивее к изменениям иерархии.

📜 Как можно встретить в старом коде

Явный вызов родителя по имени работает, но жёстко привязывает дочерний класс к конкретному базовому. Проверка через type() ломается, если появляются наследники.

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


class Programmer(Employee):
    def __init__(self, name, language):
        Employee.__init__(self, name)  # явный вызов по имени
        self.language = language


def process(obj):
    if type(obj) == Programmer:  # не учитывает наследников
        obj.write_code()
    elif type(obj) == Employee:
        obj.work()

❌ Почему этот подход может быть не лучшим сейчас

  • Имя родителя зашито в коде: при смене базового класса придётся править все дочерние.
  • Employee.__init__(self, name) не учитывает MRO и может сломаться при множественном наследовании.
  • type(obj) == Class возвращает False для объектов подклассов, что противоречит идее полиморфизма.
  • Без аннотаций типов IDE и линтеры не подскажут ожидаемые аргументы.

✅ Рекомендуемый современный вариант

Используйте super() для вызова родителя и isinstance() для проверки типов. Добавьте аннотации для ясности.

class Employee:
    def __init__(self, name: str) -> None:
        self.name = name


class Programmer(Employee):
    def __init__(self, name: str, language: str) -> None:
        super().__init__(name)
        self.language = language


def process(obj: Employee) -> None:
    if isinstance(obj, Programmer):
        print(f"{obj.name} programs in {obj.language}")
    else:
        print(f"{obj.name} works")


process(Programmer("Alice", "Python"))
process(Employee("Bob"))

Что улучшилось:

  • super() не зависит от имени родителя и корректно работает в сложных иерархиях.
  • isinstance() учитывает наследование и не ломается для подклассов.
  • Аннотации типов делают интерфейс понятным без чтения тела методов.
  • Код легче расширять: новые классы-наследники работают существующими функциями.

🕰️ Когда старый подход ещё можно встретить

В legacy-проектах и старых учебных материалах часто встречается Parent.method(self). Главное — понимать его ограничения: жёсткая привязка к имени родителя и игнорирование MRO. При рефакторинге заменяйте такие вызовы на super(), а сравнения type() — на isinstance().

Проверить по документации: про super() см. Built-in Functions, про MRO — Multiple Inheritance.