6.1 Наследование — это просто
Наследование — это фундаментальная концепция объектно-ориентированного программирования (ООП), которая позволяет одному классу (называемому дочерним или подклассом) наследовать поля и методы другого класса (называемого родительским или суперклассом).
Такой подход позволяет создавать более общие классы и повторно использовать код, улучшая организацию и поддерживаемость кода.
Зачем нужно наследование?
Допустим, вам нужно написать какой-то код, и вы решили сделать это в виде класса. Потом вы узнали, что в вашем проекте уже существует класс, который делает почти всё, что нужно вам в вашем классе. Вы можете просто скопировать код этого класса в свой и пользоваться в своё удовольствие.
А можете «как бы скопировать». Вы можете объявить тот класс родителем своего класса, и тогда Python добавит вашему классу поведение класса родителя.
Представьте, что вы природа и хотите создать Собаку. Что будет быстрее — создать собаку из бактерии за миллиард лет или одомашнить волка за 200 тысяч лет?
Пример базового наследования
Допустим, у вас есть родительский класс Animal с полем name:
class Animal:
def __init__(self, name):
self.name = name
Мы хотим создать 2 класса-наследника для него — Dog и Cat, а также добавить им обоим метод speak:
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
В примере выше дочерние классы Dog и Cat наследуют от Animal и добавляют метод speak.
Класс Dog:
- Наследует атрибут
nameизAnimal. - Добавляет метод
speak, возвращающий строку, специфичную для собак.
Класс Cat:
- Наследует атрибут
nameизAnimal. - Добавляет метод
speak, возвращающий строку, специфичную для кошек.
Финальный вариант кода выглядит так:
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Выведет: Buddy says Woof!
print(cat.speak()) # Выведет: Whiskers says Meow!
В этом примере Animal является родительским классом, а Dog и Cat — дочерними классами. Дочерние классы наследуют атрибут name и метод __init__ от родительского класса Animal, но добавляют методы speak.
6.2 Под капотом у наследования
Если вы добавили своему классу родителя, то это очень похоже на то, как будто вы скопировали код класса родителя в свой класс.
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name):
super().__init__(name) # Вызов конструктора родительского класса
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def __init__(self, name):
super().__init__(name) # Вызов конструктора родительского класса
def speak(self):
return f"{self.name} says Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Выведет: Buddy says Woof!
print(cat.speak()) # Выведет: Whiskers says Meow!
Это не совсем точное описание, но если вы никогда не сталкивались с концепцией наследования, то можете пока думать о ней в таком ключе. Дальше мы добавим сюда ещё деталей.
6.3 Иерархия наследования
Очень часто, когда проектируется сложная модель большой группы классов, вы можете столкнуться с целой иерархией наследования.
Например, у вас есть класс Animal — это базовый класс для всех животных:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement abstract method")
Мы даже добавили ему метод speak, но так как абстрактное животное не говорит, этот метод просто кидает исключение NotImplementedError — это стандартная практика.
Затем мы добавляем промежуточные классы, которые соответствуют категориям животных: Mammal — это млекопитающие и Bird для птиц.
class Mammal(Animal):
def __init__(self, name, fur_color):
super().__init__(name) # Вызов конструктора родительского класса
self.fur_color = fur_color
class Bird(Animal):
def __init__(self, name, wing_span):
super().__init__(name) # Вызов конструктора родительского класса
self.wing_span = wing_span
def fly(self):
return f"{self.name} is flying with a wingspan of {self.wing_span} meters."
И наконец, только на финальном этапе появляются классы конкретных видов животных:
class Dog(Mammal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Mammal):
def speak(self):
return f"{self.name} says Meow!"
class Parrot(Bird):
def speak(self):
return f"{self.name} says Squawk!"
Вот с ними обычно и работает конечный код:
animals = [Dog("Buddy", "brown"), Cat("Whiskers", "white"), Parrot("Polly", 0.5)]
for animal in animals:
print(animal.speak())
print(f"{dog.name} has {dog.fur_color} fur.") # Выведет: Buddy has brown fur.
print(f"{cat.name} has {cat.fur_color} fur.") # Выведет: Whiskers has white fur.
print(parrot.fly()) # Выведет: Polly is flying with a wingspan of 0.5 meters.
Хотя технически нет запретов на создание иерархий с десятками предков, важно помнить, что без необходимости лучше придерживаться простоты. В простоте сила.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ