Перейти к содержимому
Теория · Классы · урок 7 из 11

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

Представьте, что у вас уже есть чертёж «животное»: кличка, звук и умение говорить. Теперь нужен чертёж кошки. Перерисовывать всё заново незачем. Можно взять копию старого чертежа и приписать сбоку: «всё как у животного, но говорит иначе».

Без такой возможности код пришлось бы копировать. У кошки и собаки, как у любого животного, есть кличка и звук, и говорят они почти одинаково. Если копировать __init__ и методы в каждый класс, одну и ту же правку потом придётся вносить в нескольких местах. Класс может взять всё у другого класса и изменить только то, что отличается:

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

    def speak(self):
        return f"{self.name} says {self.sound}"


class Cat(Animal):
    def speak(self):
        return f"{self.name} purrs"


print(Animal("Ox").speak())  # Ox says ...
print(Cat("Tom").speak())  # Tom purrs

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

Запись class Cat(Animal) означает наследование: Cat — дочерний класс, Animal — родительский. Дочерний класс получает все методы и атрибуты класса родителя.

Давайте разберём, что происходит при вызове Cat("Tom"):

  1. Своего __init__ у Cat нет, поэтому Python берёт __init__ класса Animal.
  2. Этот __init__ создаёт у кошки атрибуты name и sound.
  3. Cat("Tom").speak() вызывает speak класса Cat и возвращает "Tom purrs".

Переопределение метода

Python ищет метод сначала в классе экземпляра, потом в родительском. Метод speak класса Cat переопределяет метод родителя: для кошки находится он.

Поэтому один и тот же вызов работает с объектами разных классов, и каждый объект выполняет свой метод:

Python
pets = [Animal("Ox"), Cat("Tom")]
print([pet.speak() for pet in pets])  # ['Ox says ...', 'Tom purrs']
print(isinstance(pets[1], Animal), isinstance(pets[0], Cat))  # True False

Экземпляр дочернего класса считается и экземпляром родительского: isinstance(pets[1], Animal) даёт True. Обратное неверно: Animal("Ox") — не Cat.

Код, который вызывает pet.speak(), класс питомца не проверяет: новому классу животных хватит своего метода speak. На собеседовании это называют полиморфизмом: один вызов, а поведение зависит от класса объекта.

Вызов super()

Если дочернему классу нужен свой __init__, он заменяет __init__ родителя целиком: Python сам вызывает только __init__ дочернего класса. Чтобы родительский код тоже выполнился, его вызывают через super():

Python
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed
        self.sound = "woof"


rex = Dog("Rex", "husky")
print(rex.speak(), rex.breed)  # Rex says woof husky

Разберём __init__ класса Dog по строкам:

  1. super().__init__(name) запускает __init__ класса Animal для того же self: у rex появляются name и sound со значением "...".
  2. self.breed = breed добавляет породу.
  3. self.sound = "woof" заменяет звук.

Побеждает последнее присваивание: если задать sound до вызова super(), родительский __init__ снова запишет в него "...". Через super() вызывают и другие методы родителя: super().speak() внутри метода Dog вернёт текст метода Animal.

Здесь аналогия с копией чертежа заканчивается. Дочерний класс не копирует методы родителя к себе. Он хранит ссылку на родителя, и Python каждый раз ищет метод по цепочке: сначала в Dog, потом в Animal.

Частая ошибка

В дочернем __init__ забывают вызвать super().__init__():

Python
class Bird(Animal):
    def __init__(self, name):
        self.sound = "tweet"  # ✗


print(Bird("Kiwi").speak())
TEXT
AttributeError: 'Bird' object has no attribute 'name'

Свой __init__ заменил родительский, и строка, которая задаёт self.name, не выполнилась. Если у родителя есть свой __init__, первой строкой дочернего __init__ пишите super().__init__(name).

Главное

  • class Cat(Animal) — наследование: дочерний класс получает методы и атрибуты родителя.
  • Метод с тем же именем в дочернем классе переопределяет метод родителя.
  • Свой __init__ заменяет родительский целиком, поэтому его код вызывают через super().__init__(...).
  • Экземпляр дочернего класса — это и экземпляр родительского: isinstance даёт True.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом