Опирается на правила:
PY-7.1…PY-7.4,PY-7.X1,PY-7.X2из Python Style Guide → раздел 7. Комментарии.
Важно знать
- По умолчанию — не пишем комментарии. Имена, тип-хинты и структура говорят сами.
- Inline-комментарий (
#) запрещён в production и в тестах — WHY выражается именем или структурой.- Docstring уместен только когда добавляет неочевидный контракт: инвариант, единицы, побочный эффект.
- Не цитируем коды правил в коде (
R-AGG-1,PYTS-9,AUTH-15).- Не пишем «что тут было», «removed because», «TODO до ...» — git blame авторитет.
- Закомментированный код — удалять немедленно.
# noqa/# type: ignore— только с кодом нарушения и кратким обоснованием.- Docstring пишется в императиве («Return», «Raise», «Save»), закрывающие
"""на отдельной строке для многострочных.
Комментарии в Python — это не дополнение к коду, а признак того, что код не смог сказать сам. UCP занимает жёсткую позицию: меньше комментариев, никаких inline, docstring только там, где контракт действительно неочевиден. Python выигрывает у Java в краткости — код и так читается; добавлять поверх объяснения значит не доверять читателю.
По умолчанию — не пишем
PY-7.1: имя + структура + тип-хинты первичны.
def charge(order: Order) -> None:
if order.is_paid:
return
payment_provider.charge(order)
Здесь ничего не нужно объяснять. is_paid, charge, guard clause — читается линейно. Добавление # проверяем статус перед if — шум.
Когда имя не справляется — не комментируем, а переименовываем или рефакторим:
def process(o):
if o.s == "p":
...
def fulfill_order(order: Order) -> None:
if order.status == OrderStatus.PAID:
...
Второй вариант не нуждается в комментарии — имя и тип говорят всё.
Docstring уместен только когда добавляет контракт
PY-7.4: docstring — не пересказ сигнатуры, не для каждой функции.
Docstring нужен, когда есть неочевидная информация, которую нельзя выразить именем или типом:
class OrderRepository(Protocol):
async def find_by_customer(
self,
customer_id: UUID,
*,
include_cancelled: bool = False,
) -> list[Order]:
"""Return orders sorted by created_at desc, newest first.
Cancelled orders are excluded by default; pass include_cancelled=True
to include them. Does not load line items — use find_with_items for that.
"""
Здесь docstring добавляет три вещи, которые не видны из сигнатуры: порядок сортировки, поведение по умолчанию для include_cancelled и предупреждение про lazy-load.
Docstring не нужен, если он пересказывает сигнатуру:
async def find_by_id(self, order_id: UUID) -> Order | None:
"""Find order by id. Returns None if not found."""
Order | None уже говорит «вернёт None если не найдено». Docstring — шум.
Формат docstring
PEP 257: первая строка — краткий императив, закрывающие """ отдельно для многострочных.
async def calculate_total(order: Order) -> Decimal:
"""Return total price including VAT, rounded to 2 decimal places."""
async def reserve_stock(
product_id: UUID,
quantity: int,
*,
timeout_seconds: int = 30,
) -> None:
"""Reserve product stock for a pending order.
Raises StockReservationError if stock is insufficient.
Reservation expires after timeout_seconds if not confirmed.
Side effect: publishes StockReservedEvent to the outbox.
"""
Что входит в docstring как неочевидный контракт:
- Инвариант («sorted by created_at desc»).
- Единицы («timeout_seconds», «rounded to 2 decimal places»).
- Побочный эффект («publishes StockReservedEvent»).
- Условия исключений, если они не очевидны из имени.
Не цитируем коды правил
PY-7.2: коды живут в гайде и commit, не в коде.
# AVOID
class OrderAggregate:
def confirm(self) -> None:
# R-AGG-1: только из DRAFT
if self.status != OrderStatus.DRAFT:
raise InvalidStatusError(self.status)
...
Причины:
Дублирует source-of-truth. Правило живёт в спеке или style-guide. Если код противоречит — правило авторитет. Комментарий с кодом — дублирующая запись, которая устаревает молча.
Хрупко. При следующей ревизии гайда нумерация уезжает: R-AGG-1 стало R-AGG-3. Комментарии в коде становятся ложью — тысячи мест, никто не обновляет.
Шум. Соответствие правилу выражается структурой. @dataclass(frozen=True, slots=True) — это уже PY-8.2. Protocol для порта — уже PY-6.3. Явная пометка «соответствует PY-8.2» избыточна.
Где живут коды правил:
- Commit messages:
PY-7.1: remove inline comments from order handler. - PR description: «Применяет PY-8.2 — переводит carrier'ы на frozen dataclass».
- Style-guide: ссылается на собственные коды.
Не «что тут было», «убрано для X»
PY-7.3: git blame — авторитетный источник.
# AVOID
class ProductService:
async def create(self, command: CreateProduct) -> Product:
# previously used sync session
# removed redis cache because of consistency issues
# TODO: restore after migration to v2
product = Product.create(command)
await self._repository.save(product)
return product
Все три комментария — шум:
- «previously used sync session» — видно из git blame, коммит объяснит почему.
- «removed redis cache» — то же.
- «TODO: restore after migration to v2» — если нужен, он должен быть задачей в issue tracker, не строкой в коде.
class ProductService:
async def create(self, command: CreateProduct) -> Product:
product = Product.create(command)
await self._repository.save(product)
return product
Закомментированный код
PY-7.X1: удалять немедленно.
# AVOID
async def find_active_customers(self) -> list[Customer]:
# customers = await self._session.execute(
# select(CustomerModel).where(CustomerModel.active == True)
# )
# return [to_domain(c) for c in customers.scalars()]
return await self._cache.get_active_customers()
Закомментированный код — не «на всякий случай». Он:
- Шумит в review и diff.
- Гниёт — через месяц не работает с новыми типами.
- Говорит читателю «кто-то не был уверен» — дестабилизирует.
Git помнит удалённое — git log -p покажет и код, и commit, и объяснение.
noqa и type: ignore — с обоснованием
PY-7.X2: # noqa/# type: ignore без кода и обоснования запрещены.
# AVOID
result = some_external_function() # type: ignore
long_url = "https://very-long-url-that-exceeds-line-length-limit.example.com/api/v2/endpoint" # noqa
# PREFER
result: SomeType = some_external_function() # type: ignore[return-value] # justify: third-party stub incomplete
long_url = (
"https://very-long-url-that-exceeds-line-length-limit"
".example.com/api/v2/endpoint"
)
Правила:
# type: ignore[<code>]— указывать конкретный код ошибки mypy, добавлять# justify: ...если причина неочевидна.# noqa: <CODE>— указывать конкретный ruff-код, не голый# noqa.- Голый
# noqaдопускается только дляE501(длина строки), если строку объективно нельзя разбить (URL, бинарные данные).
Исключение из PY-7.1 — именно # type: ignore и # noqa это допустимые комментарии-директивы инструменту, не пояснения для человека. Но только с кодом и обоснованием (PY-RUFF-3).
Когда WHY всё-таки нужен
PY-7.1 допускает нарушение, если оно улучшает читаемость (PY-1.1). Но ревьюер обязан объяснить, чем именно нарушение лучше. На практике это узкие случаи:
async def find_locked(self, order_id: UUID) -> Order | None:
# FOR UPDATE SKIP LOCKED не работает с CTE в PostgreSQL < 14.
# При апгрейде до 14 заменить на selectinload + with_for_update(skip_locked=True).
stmt = (
select(OrderModel)
.where(OrderModel.id == order_id)
.with_for_update(skip_locked=True)
)
...
Это workaround для конкретного поведения базы данных. Имя метода и типы не могут это выразить — здесь комментарий оправдан. Обратите внимание: комментарий объясняет почему именно так, а не что делает код.
Что считается WHY:
- Workaround для известного бага базы данных, библиотеки, платформы.
- Compliance или security-рассуждение («401 vs 403: клиент должен сделать refresh, не relogin»).
- Performance trade-off («linear scan быстрее для < 5 элементов»).
- Hidden constraint («thread-safe только если caller держит lock»).
Что не WHY:
- «Создаём объект» — видно по
Product.create(...). - «Возвращаем результат» — видно по
return. - «Перебираем список» — видно по
for.
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
# загружаем заказ перед await repo.find(...) | PY-7.1 | имя метода говорит само |
# R-AGG-1: только из DRAFT в коде | PY-7.2 | commit message / PR description |
# removed because ..., # TODO до v2 | PY-7.3 | git blame / issue tracker |
| Docstring «Return order by id. Returns None if not found.» | PY-7.4 | -> Order \| None уже это говорит |
| Закомментированный блок кода | PY-7.X1 | удалить, git помнит |
# type: ignore без кода и justify | PY-7.X2 | # type: ignore[return-value] # justify: ... |
# noqa без кода нарушения | PY-7.X2 | # noqa: E501 |
Куда дальше
- Именование — хорошее имя устраняет необходимость в комментарии.
- Форматирование — структура кода как форма документации.
- Тайп-хинты —
X | None,Protocol, аннотации как машиночитаемая документация. - Enforcement через ruff + mypy —
PY-RUFF-3покрываетnoqa/type: ignoreбез кода.