Абстрактные базовые классы (ABC) и протоколы
Абстрактные базовые классы помогают описывать «контракты»: набор методов, которые обязаны реализовать наследники. Это удобно, когда важно гарантировать наличие определённого поведения. Протоколы (typing.Protocol) — похожая идея, но без обязательного наследования: главное, чтобы у объекта были нужные методы и поля.
ABC: задаём контракт через @abstractmethod
from abc import ABC, abstractmethod
class HockeyRole(ABC):
@abstractmethod
def role(self) -> str:
"""Должен вернуть текст роли"""
# Нельзя создать экземпляр абстрактного класса — нет реализации role()
try:
HockeyRole()
except TypeError as e:
print('Ошибка:', e)
class HockeyPlayer(HockeyRole):
def __init__(self, name: str):
self.name = name
def role(self) -> str: # реализуем контракт
return 'Игрок'
class Goalkeeper(HockeyRole):
def __init__(self, name: str):
self.name = name
def role(self) -> str:
return 'Вратарь'
def announce(entity: HockeyRole) -> None:
print(entity.role())
announce(HockeyPlayer('Иван'))
announce(Goalkeeper('Павел'))
Ошибка: Can't instantiate abstract class HockeyRole with abstract method role
Игрок
Вратарь
Протоколы: «утиная типизация» с подсказкой типов
Протокол описывает, что у объекта должно быть (методы/поля), но объект не обязан наследоваться от какого‑то базового класса, чтобы «подойти».
from typing import Protocol
class HasRole(Protocol):
name: str
def role(self) -> str: ...
class Referee:
def __init__(self, name: str):
self.name = name
def role(self) -> str:
return 'Судья'
def make_badge(x: HasRole) -> str:
# Аннотация типа подсказывает ожидаемый «контракт» (name + role())
return f"{x.role()}: {x.name}"
print(make_badge(Referee('Олег')))
print(make_badge(HockeyPlayer('Иван'))) # тоже подходит — есть name и role()
Судья: Олег
Игрок: Иван
Протоколы помогают статическим анализаторам (mypy, pyright) находить ошибки на этапе проверки кода. В рантайме это обычные объекты; важно лишь, чтобы у них были нужные атрибуты и методы.