Каково назначение Protocol в модуле typing в Python

Ответ

Protocol из модуля typing позволяет реализовывать статическую утиную типизацию (structural subtyping). Он определяет интерфейс, которому класс может соответствовать, не наследуясь от него явно. Достаточно, чтобы класс имел те же методы и атрибуты, что и протокол.

Это позволяет писать более гибкий и слабосвязанный код, так как функции могут принимать любой объект, соответствующий нужной структуре, а не только экземпляры конкретного класса.

Ключевое отличие от ABC (Abstract Base Classes):

  • ABC: требует явного наследования и регистрации (isinstance работает).
  • Protocol: не требует наследования, проверка происходит статически (например, с помощью mypy) на основе структуры класса.

Пример:

from typing import Protocol

# Определяем протокол: любой объект с методом go() -> str
class Movable(Protocol):
    def go(self) -> str:
        ...

# Класс Car не наследуется от Movable, но соответствует протоколу
class Car:
    def go(self) -> str:
        return "Driving a car"

# Класс Person тоже соответствует
class Person:
    def go(self) -> str:
        return "Walking as a person"

# Функция принимает любой объект, подходящий под протокол Movable
def move_object(obj: Movable):
    print(obj.go())

# Статический анализатор типов не выдаст ошибок
move_object(Car())
move_object(Person())

Для проверки соответствия протоколу во время выполнения можно использовать декоратор @runtime_checkable.

Ответ 18+ 🔞

А, слушай, вот эта вся хуйня с Protocol из typing — это ж, блядь, утиная типизация, только для умников со статическими анализаторами! Суть в том, что ты описываешь, как объект должен выглядеть, а не от кого он должен родиться.

Представь, ты говоришь: «Мне нужна хрень, у которой есть метод go(), который возвращает строку». И похуй, что это за класс — машина, человек или мартышка на самокате. Если у него есть этот метод, значит, он подходит. Это и есть протокол, ёпта.

Чем это отличается от этих ваших ABC, спросишь ты?

  • ABC (Abstract Base Classes): Это как официальная бумага о родстве. Надо явно написать class Car(Movable). Тогда isinstance(car, Movable) вернёт True. Формальность, блядь.
  • Protocol: А это как негласная договорённость. «Ты не мой сын, но если ты ходишь, говоришь и материшься как я — то похуй, считайся своим». Проверяет это не Питон в рантайме по умолчанию, а всякие mypy, когда код проверяют.

Вот, смотри, пример, чтобы совсем пиздец стало понятно:

from typing import Protocol

# Вот наш протокол. Говорим: "Всё, что имеет метод go() -> str, считается Movable"
class Movable(Protocol):
    def go(self) -> str:
        ...  # Тут просто заглушка, блядь

# Класс Машина. Нигде не сказано, что он наследник Movable. Он просто есть.
class Car:
    def go(self) -> str:
        return "Driving a car"

# Класс Человек. Тоже нихуя не наследует.
class Person:
    def go(self) -> str:
        return "Walking as a person"

# А эта функция принимает что угодно, что подходит под протокол Movable.
def move_object(obj: Movable):
    print(obj.go())

# И вот тут статический анализатор (типа mypy) посмотрит и скажет:
# "Ага, и Car, и Person имеют метод go(), который возвращает str. Значит, они оба — Movable. Охуенно, пропускаю!"
move_object(Car())      # Выведет: Driving a car
move_object(Person())   # Выведет: Walking as a person

А если хочешь вот прямо во время работы программы проверить, соответствует ли объект протоколу, то надо использовать @runtime_checkable. Без него isinstance(obj, Movable) тебе просто пизда даст False, потому что Питон не видит явного наследования. Но это уже, как говорится, нюансы, блядь.