Наследование позволяет создавать новые классы на основе существующих, переиспользуя код и расширяя функциональность. Python поддерживает множественное наследование, абстрактные классы и протоколы.
Базовое наследование
class Animal:
"""Base class for all animals."""
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
def speak(self) -> str:
return "..."
def info(self) -> str:
return f"{self.name}, {self.age} лет"
class Dog(Animal):
"""Dog inherits from Animal."""
def __init__(self, name: str, age: int, breed: str) -> None:
super().__init__(name, age) # call parent __init__
self.breed = breed
def speak(self) -> str: # override parent method
return "Гав!"
def fetch(self, item: str) -> str: # new method
return f"{self.name} принёс {item}"
class Cat(Animal):
def speak(self) -> str:
return "Мяу!"
# Usage
dog = Dog("Бобик", 3, "Овчарка")
cat = Cat("Мурка", 5)
print(dog.speak()) # Гав!
print(cat.speak()) # Мяу!
print(dog.info()) # Бобик, 3 лет (inherited)
print(dog.fetch("палку")) # Бобик принёс палку
# isinstance and issubclass
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
print(isinstance(cat, Dog)) # False
print(issubclass(Dog, Animal)) # True
super() -- вызов родительского метода
class Shape:
def __init__(self, color: str = "чёрный") -> None:
self.color = color
def describe(self) -> str:
return f"Фигура цвета {self.color}"
class Circle(Shape):
def __init__(self, radius: float, color: str = "красный") -> None:
super().__init__(color) # call Shape.__init__
self.radius = radius
def describe(self) -> str:
base = super().describe() # call Shape.describe
return f"{base}, круг с радиусом {self.radius}"
def area(self) -> float:
import math
return math.pi * self.radius ** 2
circle = Circle(5.0)
print(circle.describe())
# Фигура цвета красный, круг с радиусом 5.0
print(f"Площадь: {circle.area():.2f}")
# Площадь: 78.54
Множественное наследование и MRO
class A:
def method(self) -> str:
return "A"
class B(A):
def method(self) -> str:
return "B"
class C(A):
def method(self) -> str:
return "C"
class D(B, C):
pass
d = D()
print(d.method()) # 'B' (first in MRO after D)
# Method Resolution Order (MRO) - C3 linearization
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
# MRO determines the order super() follows
class A:
def method(self) -> str:
return "A"
class B(A):
def method(self) -> str:
return f"B -> {super().method()}"
class C(A):
def method(self) -> str:
return f"C -> {super().method()}"
class D(B, C):
def method(self) -> str:
return f"D -> {super().method()}"
d = D()
print(d.method()) # D -> B -> C -> A
# super() follows MRO, not just the direct parent!
Миксины (Mixins)
import json
from datetime import datetime
class JsonMixin:
"""Mixin that adds JSON serialization capability."""
def to_json(self) -> str:
return json.dumps(self.__dict__, default=str, ensure_ascii=False)
@classmethod
def from_json(cls, json_str: str):
data = json.loads(json_str)
return cls(**data)
class TimestampMixin:
"""Mixin that adds timestamp tracking."""
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.created_at = datetime.now()
self.updated_at = datetime.now()
def touch(self) -> None:
"""Update the modification timestamp."""
self.updated_at = datetime.now()
class LogMixin:
"""Mixin that adds logging capability."""
def log(self, message: str) -> None:
class_name = type(self).__name__
print(f"[{class_name}] {message}")
# Combine mixins with a base class
class User(JsonMixin, LogMixin):
def __init__(self, name: str, email: str) -> None:
self.name = name
self.email = email
def __repr__(self) -> str:
return f"User({self.name!r}, {self.email!r})"
user = User("Иван", "[email protected]")
print(user.to_json())
# {"name": "Иван", "email": "[email protected]"}
user.log("Пользователь создан")
# [User] Пользователь создан
Абстрактные классы (ABC)
from abc import ABC, abstractmethod
import math
class Shape(ABC):
"""Abstract base class for geometric shapes."""
@abstractmethod
def area(self) -> float:
"""Calculate the area of the shape."""
...
@abstractmethod
def perimeter(self) -> float:
"""Calculate the perimeter of the shape."""
...
def describe(self) -> str:
"""Non-abstract method (inherited by all subclasses)."""
return (
f"{type(self).__name__}: "
f"площадь={self.area():.2f}, периметр={self.perimeter():.2f}"
)
# Cannot instantiate abstract class
# shape = Shape() # TypeError: Can't instantiate abstract class
class Circle(Shape):
def __init__(self, radius: float) -> None:
self.radius = radius
def area(self) -> float:
return math.pi * self.radius ** 2
def perimeter(self) -> float:
return 2 * math.pi * self.radius
class Rectangle(Shape):
def __init__(self, width: float, height: float) -> None:
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)
# Usage with polymorphism
shapes: list[Shape] = [
Circle(5),
Rectangle(3, 4),
Circle(10),
]
for shape in shapes:
print(shape.describe())
# Circle: площадь=78.54, периметр=31.42
# Rectangle: площадь=12.00, периметр=14.00
# Circle: площадь=314.16, периметр=62.83
# Total area (polymorphism in action)
total = sum(s.area() for s in shapes)
print(f"Общая площадь: {total:.2f}")
Абстрактные свойства и методы класса
from abc import ABC, abstractmethod
class Database(ABC):
"""Abstract database interface."""
@property
@abstractmethod
def connection_string(self) -> str:
"""Return the database connection string."""
...
@abstractmethod
def connect(self) -> None:
...
@abstractmethod
def execute(self, query: str) -> list[dict]:
...
@classmethod
@abstractmethod
def from_config(cls, config: dict) -> "Database":
...
class PostgreSQL(Database):
def __init__(self, host: str, port: int, dbname: str) -> None:
self.host = host
self.port = port
self.dbname = dbname
@property
def connection_string(self) -> str:
return f"postgresql://{self.host}:{self.port}/{self.dbname}"
def connect(self) -> None:
print(f"Подключение к {self.connection_string}")
def execute(self, query: str) -> list[dict]:
print(f"Выполнение: {query}")
return []
@classmethod
def from_config(cls, config: dict) -> "PostgreSQL":
return cls(config["host"], config["port"], config["dbname"])
@override (Python 3.12+)
from typing import override
class Base:
def process(self) -> str:
return "base"
def validate(self) -> bool:
return True
class Child(Base):
@override
def process(self) -> str: # OK: overrides Base.process
return "child"
# @override
# def procsess(self) -> str: # Error at type-check: typo in method name!
# return "child"
Полиморфизм
# Duck typing - "If it walks like a duck..."
class FileLogger:
def write(self, message: str) -> None:
with open("app.log", "a") as f:
f.write(message + "\n")
class ConsoleLogger:
def write(self, message: str) -> None:
print(f"[LOG] {message}")
class DatabaseLogger:
def write(self, message: str) -> None:
print(f"[DB] Saving: {message}")
# All work with the same interface - no inheritance needed!
def log_event(logger, event: str) -> None:
"""Works with any object that has a write() method."""
logger.write(f"Event: {event}")
log_event(ConsoleLogger(), "User logged in")
log_event(DatabaseLogger(), "Order created")
# Polymorphism with built-in types
def total_length(items: list) -> int:
"""Works with anything that has __len__."""
return sum(len(item) for item in items)
print(total_length(["hello", "world"])) # 10
print(total_length([[1, 2], [3, 4, 5]])) # 5
print(total_length([{1, 2, 3}, {4, 5}])) # 5
Protocol (Structural Typing)
from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawable(Protocol):
"""Any object with a draw() method."""
def draw(self) -> str: ...
@runtime_checkable
class Resizable(Protocol):
"""Any object with a resize() method."""
def resize(self, factor: float) -> None: ...
class Circle:
def __init__(self, radius: float) -> None:
self.radius = radius
def draw(self) -> str:
return f"Circle(r={self.radius})"
def resize(self, factor: float) -> None:
self.radius *= factor
class Text:
def __init__(self, content: str) -> None:
self.content = content
def draw(self) -> str:
return f"Text: {self.content}"
# Protocol check at runtime
circle = Circle(5)
text = Text("Привет")
print(isinstance(circle, Drawable)) # True
print(isinstance(text, Drawable)) # True
print(isinstance(circle, Resizable)) # True
print(isinstance(text, Resizable)) # False
# Use in type hints
def render(items: list[Drawable]) -> None:
for item in items:
print(item.draw())
render([circle, text])
# Circle(r=5)
# Text: Привет
Композиция vs наследование
# PREFER composition over inheritance
# BAD: deep inheritance hierarchy
class Animal: ...
class Mammal(Animal): ...
class Pet(Mammal): ...
class Dog(Pet): ...
class GuideDog(Dog): ... # too deep!
# GOOD: composition with delegation
class Engine:
def __init__(self, horsepower: int) -> None:
self.horsepower = horsepower
def start(self) -> str:
return f"Двигатель {self.horsepower}HP запущен"
class GPS:
def navigate(self, destination: str) -> str:
return f"Маршрут до {destination} построен"
class Car:
"""Car composed of Engine and GPS (HAS-A, not IS-A)."""
def __init__(self, model: str, engine: Engine, gps: GPS | None = None) -> None:
self.model = model
self.engine = engine # composition
self.gps = gps # optional component
def start(self) -> str:
return f"{self.model}: {self.engine.start()}"
def navigate(self, destination: str) -> str:
if self.gps is None:
return "GPS не установлен"
return self.gps.navigate(destination)
car = Car("Tesla", Engine(300), GPS())
print(car.start()) # Tesla: Двигатель 300HP запущен
print(car.navigate("Москва")) # Маршрут до Москва построен
Итоги
super()вызывает методы родителя, следуя MRO- MRO (C3-линеаризация) определяет порядок поиска при множественном наследовании
- Миксины -- небольшие классы для добавления функциональности
- ABC +
@abstractmethod-- интерфейсы через наследование - Protocol -- структурная типизация (duck typing) без наследования
@override(Python 3.12+) -- проверка переопределения методов- Предпочитайте композицию наследованию (HAS-A вместо IS-A)
- Наследование до 2-3 уровней; глубже -- используйте композицию