В чем разница между интерфейсами на основе ABC и Protocol в Python?

Ответ

Ключевое различие заключается в подходе к определению соответствия интерфейсу: 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)
Гибкость Низкая Высокая (можно "адаптировать" сторонние классы)