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