2025, Oct 19 01:16
Пробелы вокруг = при аннотациях типов, union-типах и значениях по умолчанию в Python (PEP 8)
Как по PEP 8 оформлять параметры в Python: пробелы вокруг = при аннотациях типов и значениях по умолчанию, нюансы union-типов, ruff и black для единообразия.
Расстановка пробелов вокруг значений по умолчанию в параметрах функций Python кажется пустяком, пока в дело не вступают аннотации типов. Больше всего путаницы возникает с объединениями типов, когда разработчики пробуют разные комбинации пробелов и скобок в поисках подходящего стандарта. Давайте уточним, чего на самом деле ожидает PEP 8 и как применять это последовательно.
В чём проблема
Широко распространено мнение, что в списке параметров вокруг знака равенства у значений по умолчанию не должно быть пробелов. Это верно только для неаннотированных параметров. Как только появляется аннотация, правило меняется, а с union-типами вопросы оформления множатся.
def setup(name: str="baz"):
    pass
# Какой вариант оформления правильный для объединений типов?
def process_payload(token: str | None=None):
    pass
def process_payload(token: str | None = None):
    pass
def process_payload(token: (str | None)=None):
    pass
Что на самом деле определяет стиль
PEP 8 прямо описывает, как сочетаются аннотации и значения по умолчанию. Рекомендация короткая и однозначная:
When combining an argument annotation with a default value, however, do use spaces around the
=sign
Эта единственная фраза объясняет, почему привычка «без пробелов» перестаёт работать, как только параметр аннотирован. В присутствии аннотации знак равенства должен быть окружён пробелами, тогда как у неаннотированных параметров значение по умолчанию остаётся «приклеенным». Скобки вокруг типов не являются стандартным стилем для объединений; они, как правило, не нужны, если только вы не хотите явно подчеркнуть группировку. Если вам ближе более выразительная пометка необязательного значения, в простых случаях одного типа плюс None можно использовать Optional[...], хотя этот приём не распространяется на произвольные объединения.
Как исправить
Следуя рекомендации PEP 8, сигнатуру функции с union-типом и значением по умолчанию стоит записывать с пробелами вокруг знака равенства и без лишних скобок.
def process_payload(token: str | None = None):
    pass
Если вы приводите к единому стилю крупную кодовую базу, форматер сэкономит время. Инструменты вроде ruff или black удержат единообразие пробелов и снимут соблазн спорить о стиле на ревью.
Почему это важно
Последовательное оформление аннотаций и значений по умолчанию повышает читаемость и уменьшает неоднозначность сигнатур. Это также помогает инструментам: линтеры, форматеры и типизаторы работают лучше, когда код следует общим договорённостям. Как бы мало это ни казалось, одинаковые пробелы предотвращают расползание стиля и делают диффы чище.
Что запомнить
Простое правило: у аннотированных параметров вокруг знака равенства есть пробелы, у неаннотированных — нет. Избегайте скобок у union-типов, если только специально не хотите усилить группировку. Для необязательных параметров с одним типом Optional[...] в некоторых контекстах читается яснее, чем T | None, а для более широких объединений оставайтесь с формой |. Подключите автоформатер, чтобы закрепить эти решения и сосредоточить команду на логике, а не на макете.
Статья основана на вопросе со StackOverflow от GrandeKnight и ответе от Mureinik.