Перейти к основному содержимому

Абстрактные базовые классы (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) находить ошибки на этапе проверки кода. В рантайме это обычные объекты; важно лишь, чтобы у них были нужные атрибуты и методы.