Ответ
Ключевое различие заключается в подходе к определению соответствия интерфейсу: ABC использует номинальную типизацию (явное наследование), а Protocol — структурную типизацию (утиную типизацию).
1. Интерфейс на основе abc.ABC (номинальная типизация)
Класс должен явно наследоваться от абстрактного базового класса (ABC) и реализовывать все его абстрактные методы, помеченные декоратором @abstractmethod. Соответствие проверяется во время выполнения (runtime).
Пример:
from abc import ABC, abstractmethod
class Serializable(ABC):
@abstractmethod
def serialize(self) -> bytes:
pass
# Класс User ДОЛЖЕН наследоваться от Serializable
class User(Serializable):
def serialize(self) -> bytes:
return b'user_data'
# Этот код вызовет TypeError, т.к. метод не реализован
# class Product(Serializable):
# pass
# p = Product()
2. Интерфейс на основе typing.Protocol (структурная типизация)
Классу не нужно наследоваться от протокола. Он считается совместимым, если имеет все методы и атрибуты, определённые в протоколе, с правильными сигнатурами. Проверка в основном выполняется статическими анализаторами кода (Mypy, Pyright).
Пример:
from typing import Protocol
class Serializable(Protocol):
def serialize(self) -> bytes:
...
# Класс User НЕ наследуется от Serializable, но соответствует протоколу
class User:
def serialize(self) -> bytes:
return b'user_data'
# Статический анализатор проверит, что obj имеет метод .serialize()
def save(obj: Serializable):
data = obj.serialize()
print(f"Saving {len(data)} bytes")
save(User()) # Ошибок нет
Итог
| Критерий | abc.ABC |
typing.Protocol |
|---|---|---|
| Типизация | Номинальная | Структурная |
| Наследование | Обязательно | Не требуется |
| Проверка | Runtime | Static (Mypy) |
| Гибкость | Низкая | Высокая (можно "адаптировать" сторонние классы) |