Опирается на правила: PY-2.1PY-2.7, PY-2.X1, PY-2.X2 из Python Style Guide → раздел 2. Именование.

Важно знать

  • Модули и пакетыsnake_case, короткие, без дефисов и заглавных.
  • КлассыPascalCase-существительные; исключения оканчиваются на Error.
  • Функции, методы, переменныеsnake_case; функции и методы — глагол-действие.
  • КонстантыUPPER_SNAKE_CASE на уровне модуля.
  • Приватность — один ведущий _; два __ только для name-mangling в иерархиях, не «ради защиты».
  • Запрещены l, O, I как однобуквенные имена — неотличимы от цифр.
  • Pydantic-поля, затеняющие builtins (id, type), — через суффикс _ и Field(alias=...).
  • Имена тестовtest_<action>_when_<condition>_<expected>, говорящие, без сокращений.

Именование — первый уровень документации: хорошее имя устраняет необходимость в комментарии (PY-7.1). В Python конвенции плоские и однозначные — PEP 8 оставляет мало пространства для разночтений. ruff с набором N ловит большинство механических нарушений; семантику — ucp-py-style-review.

Модули и пакеты

PY-2.1: snake_case, короткие имена без дефисов, без заглавных букв.

order_repository.py    # ✓
product_service.py     # ✓
customer/              # ✓ — пакет

orderRepository.py     # ✗ — camelCase
order-repository.py    # ✗ — дефис
OrderRepository.py     # ✗ — PascalCase

Пакеты без лишних слов: order, customer, payment — не order_utils, не managers. Суффиксы utils/helpers/common — сигнал размытой ответственности; перенести в пакет по доменной роли.

Классы

PY-2.2: PascalCase, существительные; исключения оканчиваются на Error.

class Order: ...
class OrderRepository: ...
class ProductNotFoundError(Exception): ...
class InsufficientStockError(ValueError): ...

class order: ...              # ✗ — строчный
class IOrderRepository: ...   # ✗ — I-префикс (C#-конвенция)
class OrderService2: ...      # ✗ — цифровой суффикс без смысла
class PaymentFailed(Exception): ...  # ✗ — исключение не на Error

Интерфейсы (порты в гексагональной архитектуре) — Protocol-классы, тоже без I-префикса:

from typing import Protocol

class OrderRepository(Protocol):
    async def save(self, order: Order) -> None: ...
    async def find_by_id(self, order_id: UUID) -> Order | None: ...

Функции и методы

PY-2.3: snake_case, глагол-действие.

def create_order(customer_id: UUID, items: list[OrderItem]) -> Order: ...
def find_by_customer(customer_id: UUID) -> list[Order]: ...
def calculate_total(order: Order) -> Decimal: ...
def is_eligible_for_refund(order: Order) -> bool: ...

def order(): ...              # ✗ — существительное вместо глагола
def OrderCreate(): ...        # ✗ — PascalCase + существительное
def processdata(): ...        # ✗ — слитно, нет разделителя

Для булевых предикатов — is_, has_, can_:

def is_paid(self) -> bool: ...
def has_active_subscription(self) -> bool: ...
def can_cancel(self) -> bool: ...

def paid(self) -> bool: ...   # ✗ — прилагательное, не глагол

Переменные и параметры

PY-2.3, PY-2.6: snake_case, короткие имена — только в узком скоупе.

order_id: UUID
customer_email: str
active_orders: list[Order]

for i, item in enumerate(items):
    process(item)

def find_by_id(order_id: UUID) -> Order | None: ...

Запрещены l (строчная L), O (заглавная O), I (заглавная i) как однобуквенные — неотличимы от 1, 0, 1 в большинстве шрифтов. Короткие i, n, k — только в теле цикла с узким скоупом.

Константы

PY-2.4: UPPER_SNAKE_CASE на уровне модуля.

MAX_RETRIES = 3
DEFAULT_PAGE_SIZE = 20
ORDER_EXPIRY_SECONDS = 900
PAYMENT_TIMEOUT = timedelta(minutes=15)

max_retries = 3           # ✗ — не выделено как константа
MaxRetries = 3            # ✗ — PascalCase

UPPER_SNAKE_CASE — сигнал «это значение не меняется, известно на момент импорта». Если mypy --strict используется, добавляют явный тип:

from decimal import Decimal

MAX_RETRIES: int = 3
MINIMUM_ORDER_AMOUNT: Decimal = Decimal("100.00")

Приватность

PY-2.5: один ведущий _ для внутреннего; двойной __ — только для name-mangling в иерархиях.

class OrderRepository:
    def __init__(self, session: AsyncSession) -> None:
        self._session = session        # ✓ — internal, не публичный API

    def _build_query(self, filters: dict) -> Select: ...  # ✓ — internal helper

Двойной __ (name-mangling) — для защиты атрибута от случайного переопределения в подклассе, не как замена private:

class BaseValidator:
    def __init__(self) -> None:
        self.__rules: list = []        # ✓ — защита в иерархии

class ExtendedValidator(BaseValidator):
    def add_rule(self) -> None:
        self._ExtendedValidator__rules  # name-mangled, доступ изменился

Использовать __ «ради приватности» без иерархии — избыточно и создаёт путаницу.

Pydantic-поля и builtins

PY-2.X2: поля Pydantic-моделей с именами, затеняющими builtins (id, type, list, dict) — через суффикс _ и Field(alias=...).

from pydantic import BaseModel, ConfigDict, Field
from uuid import UUID

class OrderResponse(BaseModel):
    model_config = ConfigDict(populate_by_name=True)

    id_: UUID = Field(alias="id")
    type_: str = Field(alias="type")
    customer_id: UUID
    total_amount: Decimal

Исключение: ORM-модели, domain-агрегаты и БД-DTO, где id — канонический атрибут домена:

from dataclasses import dataclass

@dataclass(frozen=True, slots=True)
class Order:
    id: UUID              # ✓ — domain aggregate, id каноничен
    customer_id: UUID
    status: OrderStatus

Pydantic-схема (response/request DTO) — через суффикс; агрегат и ORM — без.

Имена тестов

PY-2.7: структура test_<action>_when_<condition>_<expected>.

def test_create_order_when_customer_not_found_raises_not_found_error() -> None: ...

def test_calculate_total_when_discount_applied_returns_reduced_amount() -> None: ...

def test_find_by_id_when_order_exists_returns_order() -> None: ...

def test_order(): ...                  # ✗ — не говорит что проверяется
def test_create_1(): ...               # ✗ — числовой суффикс
def testCreateOrder(): ...             # ✗ — camelCase

Имя теста — это документация поведения. pytest выводит его при падении — оно должно однозначно сказать, что сломалось и при каком условии.

Что запрещено

АнтипаттернПравилоЧто взамен
class IOrderRepositoryPY-2.2class OrderRepository(Protocol)
class PaymentFailed(Exception)PY-2.2class PaymentFailedError(Exception)
def order() (существительное)PY-2.3def create_order() / def get_order()
str_name = "x" (тип в имени)PY-2.X1name: str = "x" — тип через аннотацию
class Resp(BaseModel): id: UUIDPY-2.X2id_: UUID = Field(alias="id")
self.__field без иерархииPY-2.5self._field
l = 1, O = 0, I = 1PY-2.6count = 1, zero = 0, index = 1
MAX_RETRIES = 3 как max_retries = 3PY-2.4MAX_RETRIES: int = 3
def testCreateOrder()PY-2.7def test_create_order_when_..._returns...()

Куда дальше

  • Импорты — абсолютные импорты, группировка stdlib/third-party/local, запрет wildcard.
  • Тайп-хинты — X | None вместо Optional, Protocol для портов, Decimal для денег.
  • Современный Python — match/case, @dataclass(frozen=True, slots=True), StrEnum.
  • Раздел 2. Именование — нормативные формулировки — коды PY-2.* в одном месте.