Опирается на правила:
PY-2.1…PY-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 IOrderRepository | PY-2.2 | class OrderRepository(Protocol) |
class PaymentFailed(Exception) | PY-2.2 | class PaymentFailedError(Exception) |
def order() (существительное) | PY-2.3 | def create_order() / def get_order() |
str_name = "x" (тип в имени) | PY-2.X1 | name: str = "x" — тип через аннотацию |
class Resp(BaseModel): id: UUID | PY-2.X2 | id_: UUID = Field(alias="id") |
self.__field без иерархии | PY-2.5 | self._field |
l = 1, O = 0, I = 1 | PY-2.6 | count = 1, zero = 0, index = 1 |
MAX_RETRIES = 3 как max_retries = 3 | PY-2.4 | MAX_RETRIES: int = 3 |
def testCreateOrder() | PY-2.7 | def 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.*в одном месте.