Я долго думала, что PEP — это про оформление. PEP 8: называй переменные вот так, PEP 257: пиши докстринги вот так.
Потом начала использовать их по‑настоящему и выяснилось, что часть из них вообще не про то, как выглядит код!



ABC против Protocol (nominal vs structural subtyping)

База: проектируешь сервис с несколькими источниками данных. Пишешь ABC, реализации наследуются — красота. Потом приходит сторонняя библиотека. Нужный класс есть, делает всё что нужно, но наследоваться от твоего ABC не может. Пишешь адаптер — третий лишний.

Это ловушка номинальной типизации: объект подходит, потому что он наследник, а не потому что он умеет то же самое. ABC дает твёрдую гарантию в рантайме — не реализовал абстрактный метод, получишь TypeError при инстанциировании (но только если контролируешь весь граф зависимостей).

Есть нюанс, который часто обычно упускают: ABC поддерживает __subclasshook__ — он позволяет самому ABC решать, считать ли произвольный класс своим subclass без явного наследования. collections.abc.Iterable так и работает: любой класс с __iter__ автоматически становится его виртуальным subclass. Граница между nominal и structural внутри ABC размыта сильнее, чем кажется.

PEP 544 меняет вопрос с «откуда объект» на «что он умеет». Structural subtyping, static duck typing — называй как хочешь, суть одна.

Но тут важно не промахнуться: Protocol это про статический анализ, не рантайм. isinstance() без @runtime_checkable упадёт с TypeError. С ним — работает, но проверяет только наличие атрибутов, не сигнатуры. И в горячем пути это O(n) по числу атрибутов контракта на каждый вызов.

Ещё в 3.12 появился PEP 698 и @override — явная маркировка переопределения. Если родительский метод переименован или удалён, type checker сразу скажет — это небольшая деталь сильно влияющая на надёжность иерархий.

Архитектурный выбор простой: где ловить нарушение контракта — при инстанциировании в рантайме или в CI при type checking.



# PEP 634 и __match_args__

Pattern matching в 3.10 встретили скептически: зачем усложнять язык, если это другой if/elif? Справедливо! Но не совсем точный: для mapping‑ и sequence‑паттернов CPython добавил отдлеьные опкоды: MATCH_MAPPING, MATCH_SEQUENCE, MATCH_CLASS. Это не сахар поверх условий, это отдельный путь исполнения.

Но перформанс здесь вообще не главный аргумент. Возьми обработчик событий с десятком типов и вложенностью:
 — с if/elif: контекст каждой ветки держишь в голове;
 — с match/case: пишешь образец формы данных — видно все ожидаемые структуры сразу;

Архитектурным инструментом это становится через __match_args__ (tuple[str,...]): добавляешь в класс — и match/case деструктурирует его позиционно, как датакласс. Начинаешь проектировать классы с оглядкой на то, как они будут pattern‑matched дальше по стеку.

Главная ловушка — простое имя в case‑ветке будет capture pattern, то есть привязка к имени, а не сравнение с константой:
 — case RED: не «если значение равно RED» а «привязать значение к RED»

для сравнения с константой нужен dotted name:
 — case Color.RED: тут «сравнивает с константой»

Тест не поймает — он не падает, просто тихо привязывает не то значение не туда.

Это кстати не баг — это намеренное решение из ML‑синтаксисов, где паттерн = деструктуризация. Просто нужно перестроить интуицию, чтобы не стреляло на ревью:)


И последнее: PEP 695 в 3.12 — TypeVar('T') и TypeAlias остаются для обратной совместимости, но в новом коде они больше не нужны. Генерики через [T] прямо в сигнатуре, алиасы через type. Бонус — type X =... создаёт TypeAliasType с ленивым вычислением правой части: forward references без кавычек и from __future__ import annotations.
Параметрический полиморфизм вышел из библиотеки в синтаксис!

А вы используете Protocol или всё ещё ABC? И если Protocol — @runtime_checkable ставите или доверяете статическому анализу?