Ответ
Протоколы (typing.Protocol) в Python используются для определения интерфейсов на основе структурной типизации (также известной как 'утиная типизация'). Они позволяют статическим анализаторам типов (например, mypy, pyright) проверять, соответствует ли объект определенному набору методов и атрибутов, без необходимости явного наследования.
Основные сценарии использования Protocol:
-
Абстракция без наследования: Когда нужно определить интерфейс, но нет необходимости или возможности использовать абстрактные базовые классы (
abc.ABC) или явное наследование. Это идеально для случаев, когда классы уже существуют и реализуют нужные методы, но не имеют общего предка.from typing import Protocol class Drawable(Protocol): def draw(self) -> None: ... # Определение сигнатуры метода class Circle: def draw(self) -> None: print("Drawing a circle") class Square: def draw(self) -> None: print("Drawing a square") def render(obj: Drawable) -> None: obj.draw() render(Circle()) # Работает, так как Circle соответствует протоколу Drawable render(Square()) -
Обратная совместимость и интеграция: Для типизации объектов из сторонних библиотек или унаследованного кода, где вы не можете изменить иерархию наследования, но хотите обеспечить проверку типов.
-
Аннотация callback-функций: Для четкого описания ожидаемой сигнатуры функций, передаваемых в качестве аргументов.
from typing import Protocol class StringProcessor(Protocol): def __call__(self, data: str) -> int: ... def process_data(processor: StringProcessor, text: str) -> int: return processor(text) def my_processor(s: str) -> int: return len(s) print(process_data(my_processor, "hello")) # 5 -
Гибкость в дизайне: Позволяют создавать более гибкие и слабосвязанные системы, где компоненты взаимодействуют через их поведение, а не через их иерархию классов.
Протоколы особенно полезны в сочетании с декоратором @runtime_checkable (из typing), который позволяет проверять соответствие протоколу во время выполнения с помощью isinstance() и issubclass(), что расширяет их применение за пределы статической проверки типов.
Ответ 18+ 🔞
Ну ты посмотри, что эти умники в Python придумали! Протоколы, блядь! typing.Protocol! Звучит-то как, ёпта, будто мы не код пишем, а в ООН заявку подаём.
А на деле-то что? Обычная утиная типизация, только в пиджаке и с галстуком. Если оно крякает как утка и плавает как утка — значит, это утка, и похуй, что в паспорте у неё написано «гусь» или «индюк». Вот и протоколы так же работают.
Зачем эта штука, на самом деле, нужна?
-
Чтобы не ебаться с наследованием. Ну правда, зачем городить огород из абстрактных классов, если можно просто сказать: «Эй, чувак, у тебя есть метод
draw()? Есть? Ну и отлично, ты нам подходишь, иди сюда». Это ж как в жизни — тебе же похуй, диплом у сантехника есть или нет, главное, чтобы жопу не залил.from typing import Protocol class Drawable(Protocol): def draw(self) -> None: ... # Вот такая вот заявка: «Хочу, чтобы у объекта был этот метод» class Circle: def draw(self) -> None: # А у этого круга он есть! И он даже не знает, что он в каком-то протоколе состоит! print("Рисую кружочек") class Square: def draw(self) -> None: # И квадрат тоже рисуется! Красота! print("Рисую квадратик") def render(obj: Drawable) -> None: # А эта функция говорит: «Дайте мне что угодно, что умеет рисоваться» obj.draw() render(Circle()) # И работает! Потому что круг соответствует протоколу, хоть он об этом и не догадывался. render(Square()) -
Чтобы не переписывать старый код, который писал какой-то мудак десять лет назад. У тебя есть библиотека, написанная на коленке, все классы там — сироты, без папы-мамы. А ты хочешь её типизировать. Ну так объяви протокол, скажи: «Вот такие методы я от тебя жду». И всё, статический анализатор успокоится, а код менять не надо. Волшебство, блядь.
-
Чтобы не путаться в колбэках. Раньше писали
Callable[[str], int]и думали: «Ну вроде понятно». А теперь можно дать этому колбэку имя и описать, что он делает, прямо в типе. Красота!from typing import Protocol class StringProcessor(Protocol): def __call__(self, data: str) -> int: ... # «Дайте мне функцию, которая жрёт строку и выплёвывает число» def process_data(processor: StringProcessor, text: str) -> int: return processor(text) def my_processor(s: str) -> int: # Эта функция жрёт строку и выплёвывает число. Она — процессор! return len(s) print(process_data(my_processor, "hello")) # 5 -
Просто для гибкости. Чтобы твой код зависел не от конкретного класса-родителя (который, возможно, ещё и в другой библиотеке), а от поведения. «Мне нужен не сын Ивана Ивановича, а любой, кто умеет забивать гвозди». Здорово, правда?
А ещё есть волшебный декоратор @runtime_checkable. Он позволяет во время работы программы ткнуть в объект пальцем и спросить: «Ты соответствуешь протоколу Drawable?» Через isinstance(). Это уже не просто для статических анализаторов, а реальная, живая проверка. Ёперный театр!
Короче, протоколы — это охуенно, когда надоело строить жёсткие иерархии классов и хочется просто договориться о том, кто что умеет. Как на стройке: «Ты умеешь класть кирпич? Умеешь? Ну так давай, работай».