Я долго думала, что 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 ставите или доверяете статическому анализу?
