Рекомендации по разработке на Python
Рекомендации по разработке на Python
Введение в культуру кода
Разработка на Python предполагает соблюдение определённых принципов, которые делают код понятным, поддерживаемым и расширяемым. Язык предоставляет гибкость, но именно дисциплина в оформлении и проектировании определяет качество программного продукта. Культура кода на Python формируется вокруг нескольких ключевых идей — читаемость превыше всего, простота лучше сложности, явное лучше неявного. Эти принципы воплощены в официальных руководствах по стилю и поддержаны сообществом разработчиков.
Процесс разработки начинается с получения задачи и анализа её требований. Перед написанием первой строки кода необходимо убедиться в полноте понимания поставленной цели, выявить потенциальные риски и ограничения. Даже для небольших задач полезно составить краткий план реализации — определить основные сущности, их взаимодействие и последовательность действий. Такой подход помогает избежать переделок на поздних этапах и снижает количество ошибок.
Кодирование представляет собой центральный этап разработки. Здесь важно соблюдать принятые в проекте соглашения, следить за читаемостью и структурой. Хороший код легко читается другими разработчиками, содержит минимальное количество неочевидных решений и следует принципам проектирования. После завершения написания кода обязательна его проверка — тестирование собственными силами, анализ на наличие ошибок, проверка соответствия требованиям задачи. Только после полной уверенности в корректности работы код попадает в систему контроля версий и проходит код-ревью.
Соглашения об именовании
Имена в коде служат основным средством коммуникации между разработчиками. Правильно подобранные имена передают смысл сущности без дополнительных комментариев и упрощают понимание логики программы.
Основные правила именования
Все идентификаторы в Python используют стиль snake_case — слова разделяются символом подчёркивания, все буквы строчные. Этот стиль применяется для переменных, функций, методов и модулей. Константы объявляются в стиле UPPER_CASE — все буквы заглавные, слова разделены подчёркиванием. Классы именуются в стиле PascalCase — каждое слово начинается с заглавной буквы, разделители отсутствуют.
Примеры корректных имён:
Код ITЗагрузка примера кода…
Имена должны передавать назначение сущности максимально точно. Избегайте однобуквенных имён за исключением счётчиков в циклах или математических выражений, где контекст очевиден. Предпочитайте полные слова сокращениям, если сокращение не является общепринятым в предметной области. Например, user_id предпочтительнее uid, а config допустимо как общепринятое сокращение для configuration.
Для булевых переменных и методов используйте префиксы is_, has_, can_, should_, которые делают намерение очевидным:
is_active = True
has_permissions = False
can_edit = user.role == "admin"
should_retry = attempt < max_attempts
Имена функций и методов начинаются с глагола, описывающего действие:
def calculate_total(items):
pass
def validate_user_credentials(username, password):
pass
def send_notification(user, message):
pass
Имена классов представляют собой существительные, описывающие сущность или концепцию:
class ShoppingCart:
pass
class PaymentGateway:
pass
class UserSession:
pass
Имена модулей должны быть короткими, полностью строчными, без подчёркиваний, если это возможно. Для пакетов применяются те же правила. Избегайте имён, совпадающих со стандартными библиотеками или широко используемыми сторонними пакетами.
Форматирование и оформление кода
Читаемость кода напрямую влияет на скорость его понимания и количество ошибок при модификации. Единообразное форматирование создаёт предсказуемую структуру, которую легко воспринимать визуально.
Отступы и пробелы
Для отступов используются четыре пробела. Табуляция запрещена. Отступы определяют структуру блоков кода в Python, поэтому их единообразие критически важно. После двоеточия, открывающего блок, следует новая строка с увеличенным отступом. Закрывающая строка блока возвращается на предыдущий уровень отступа.
Операторы окружены пробелами с обеих сторон:
x = a + b
threshold = value * 0.85
result = (x + y) / (z - w)
После запятых ставится один пробел, перед запятой пробел не ставится:
items = [1, 2, 3, 4, 5]
user = {"name": "Alice", "age": 30, "city": "Moscow"}
process_data(first_item, second_item, third_item)
В вызовах функций и индексации пробелы не ставятся между именем функции и открывающей скобкой, а также между именем последовательности и открывающей квадратной скобкой:
value = calculate_sum(items)
first = items[0]
Пустые строки разделяют логические блоки кода внутри функции, а также разделяют функции и классы на верхнем уровне модуля. Одна пустая строка используется между методами внутри класса. Две пустые строки — между определениями функций и классов на уровне модуля.
Длина строк и переносы
Максимальная длина строки составляет 88 символов. Это значение рекомендовано инструментом Black и обеспечивает комфортное чтение на различных устройствах. При необходимости переноса выражения на несколько строк применяются следующие подходы:
Для арифметических выражений и логических операций перенос выполняется перед оператором:
total = (first_component
+ second_component
+ third_component
- adjustment)
Для вызовов функций с большим количеством аргументов каждый аргумент размещается на отдельной строке с выравниванием:
result = process_transaction(
user_id=current_user.id,
amount=transaction_amount,
currency=transaction_currency,
timestamp=datetime.now(),
metadata=additional_info
)
Для сложных условий в операторе if каждый элемент условия размещается на отдельной строке:
if (user.is_active
and user.has_valid_subscription
and transaction.amount <= user.daily_limit
and not is_fraudulent(transaction)):
process_payment(transaction)
Оформление многострочных конструкций
Словари и списки с большим количеством элементов оформляются вертикально. Каждый элемент размещается на отдельной строке с отступом. Закрывающая скобка располагается на отдельной строке на уровне начала конструкции:
Код ITЗагрузка примера кода…
Для кортежей с одним элементом обязательно указывается запятая после элемента:
single_element = ("only_item",)
Структура проекта и организация файлов
Правильная организация файлов и директорий упрощает навигацию по проекту, облегчает тестирование и развёртывание. Структура проекта на Python следует определённым шаблонам, которые стали стандартом де-факто в сообществе.
Базовая структура приложения
Типичная структура проекта включает следующие элементы:
my_project/
├── src/
│ └── my_package/
│ ├── __init__.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── models.py
│ │ ├── services.py
│ │ └── repositories.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── endpoints.py
│ │ └── schemas.py
│ └── utils/
│ ├── __init__.py
│ ├── helpers.py
│ └── validators.py
├── tests/
│ ├── __init__.py
│ ├── unit/
│ │ ├── test_models.py
│ │ └── test_services.py
│ └── integration/
│ └── test_api.py
├── docs/
│ └── index.md
├── requirements/
│ ├── base.txt
│ ├── dev.txt
│ └── prod.txt
├── .env.example
├── .gitignore
├── pyproject.toml
├── README.md
└── setup.py
Корневая директория проекта содержит конфигурационные файлы и метаданные. Директория src содержит исходный код приложения, организованный в пакеты. Разделение на core, api, utils отражает архитектурные слои приложения. Директория tests зеркалирует структуру исходного кода, что упрощает навигацию между реализацией и тестами.
Организация модулей
Каждый модуль должен иметь чёткую ответственность. Модуль models.py содержит определения данных и бизнес-логику, связанную с ними. Модуль services.py реализует операции над данными, координирует взаимодействие между репозиториями и другими компонентами. Модуль repositories.py отвечает за доступ к данным — запросы к базе данных, кэширование, внешние API.
Избегайте создания монолитных модулей с тысячами строк кода. При достижении размера модуля 500–800 строк рассмотрите возможность его декомпозиции на несколько специализированных модулей. Например, модуль services.py можно разделить на user_services.py, payment_services.py, notification_services.py в зависимости от предметной области.
Файл __init__.py в каждом пакете определяет его публичный интерфейс. В нём можно импортировать ключевые классы и функции, чтобы обеспечить удобный доступ извне пакета:
Код ITЗагрузка примера кода…
Такой подход позволяет импортировать сущности напрямую из пакета:
from my_package.core import User, UserService
Управление зависимостями
Зависимости проекта описываются в файлах requirements/*.txt или в секции [project.optional-dependencies] файла pyproject.toml. Базовые зависимости размещаются в base.txt, зависимости для разработки — в dev.txt, для продакшена — в prod.txt. Файл dev.txt включает base.txt и добавляет инструменты для тестирования, линтинга, форматирования.
Все зависимости фиксируются с точными версиями в файле requirements.txt для продакшена. Для разработки допустимо использование диапазонов версий, но с осторожностью — чтобы избежать неожиданных изменений поведения при обновлении зависимостей.
Проектирование классов и функций
Хорошее проектирование снижает сложность системы, упрощает тестирование и делает код устойчивым к изменениям. Принципы проектирования применимы ко всем уровням — от отдельных функций до архитектуры приложения в целом.
Принцип единственной ответственности
Каждый класс и функция должны решать одну задачу. Класс User отвечает за представление пользователя и его атрибутов. Класс UserService отвечает за операции с пользователями — создание, обновление, поиск. Класс UserRepository отвечает за сохранение и загрузку пользователей из хранилища данных. Такое разделение позволяет изменять один аспект системы без влияния на другие.
Функции должны быть короткими и сфокусированными. Идеальная функция выполняет одну операцию и занимает не более 20–30 строк. Если функция растёт в размерах, её следует декомпозировать на несколько вспомогательных функций с понятными именами. Это улучшает читаемость и позволяет повторно использовать логику.
Пример декомпозиции:
Код ITЗагрузка примера кода…
Инкапсуляция и сокрытие реализации
Внутренние детали реализации класса должны быть скрыты от внешнего мира. Публичный интерфейс класса определяет контракт взаимодействия, а детали реализации могут изменяться без влияния на клиентский код. В Python для обозначения внутренних атрибутов и методов используется префикс подчёркивания _.
Код ITЗагрузка примера кода…
Клиентский код взаимодействует только с методом process_payment, не заботясь о деталях подготовки запроса, генерации подписи или обработки ответа. При изменении внутренней реализации — например, переходе на другую платежную систему — публичный интерфейс остаётся неизменным.
Композиция вместо наследования
Предпочитайте композицию наследованию при проектировании классов. Композиция обеспечивает большую гибкость и снижает связанность между компонентами. Вместо создания иерархии классов через наследование, собирайте поведение из независимых компонентов.
Код ITЗагрузка примера кода…
Композиция позволяет комбинировать компоненты различными способами, заменять их реализации без изменения клиентского кода и упрощает тестирование через внедрение заглушек.
Обработка ошибок и исключений
Корректная обработка ошибок повышает надёжность приложения и упрощает диагностику проблем. Исключения в Python следует использовать для обработки исключительных ситуаций, а не для управления нормальным потоком выполнения программы.
Создание пользовательских исключений
Для предметной области проекта создаются собственные классы исключений, наследуемые от базовых классов Exception или более специфичных исключений стандартной библиотеки. Пользовательские исключения группируются в иерархию, отражающую структуру ошибок в системе.
Код ITЗагрузка примера кода…
Использование специфичных исключений позволяет точно определить тип ошибки и обработать её соответствующим образом:
try:
order = order_service.create_order(order_data)
except ValidationError as e:
return {"error": "validation_failed", "field": e.field, "message": e.message}, 400
except PaymentError as e:
return {"error": "payment_failed", "transaction_id": e.transaction_id}, 402
except ResourceNotFoundError as e:
return {"error": "resource_not_found", "type": e.resource_type, "id": e.resource_id}, 404
Логирование ошибок
Каждое исключение, достигающее верхнего уровня обработки, должно быть записано в лог с полным контекстом — тип исключения, сообщение, стек вызовов, идентификаторы запроса и пользователя. Это обеспечивает возможность диагностики проблем в продакшене.
Код ITЗагрузка примера кода…
Избегайте пустых блоков except. Если исключение перехватывается без обработки, это скрывает реальную проблему и затрудняет отладку. Даже в случаях, когда ошибка игнорируется намеренно, добавляйте комментарий с объяснением причины.
Комментирование и документирование
Комментарии дополняют код, объясняя решения, которые неочевидны из самого кода. Хороший код самодокументирован через имена и структуру, но комментарии необходимы для объяснения "почему" принято то или иное решение.
Документирование модулей и функций
Каждый модуль начинается со строк документации, описывающих его назначение и основные компоненты. Каждая публичная функция и метод сопровождается строкой документации в формате Google Style или NumPy Style. Строка документации содержит краткое описание, параметры, возвращаемое значение и возможные исключения.
Код ITЗагрузка примера кода…
Комментарии в коде
Комментарии в теле функции используются для пояснения нетривиальных алгоритмов, объяснения причин обхода ограничений или описания временных решений. Избегайте комментариев, дублирующих код:
# Плохо: комментарий дублирует очевидное действие
# Увеличиваем счётчик на единицу
counter += 1
# Хорошо: комментарий объясняет нетривиальное решение
# Используем экспоненциальную задержку для предотвращения шторма запросов
# при частых временных сбоях внешнего сервиса
delay = min(base_delay * (2 ** attempt), max_delay)
time.sleep(delay)
Для временных решений или известных ограничений используйте маркеры TODO, FIXME, HACK с указанием причины и плана устранения:
# TODO — Заменить хардкод значения на конфигурационный параметр после релиза v2.0
MAX_RETRIES = 3
# FIXME — Обход ошибки в библиотеке requests версии 2.28.0
# Удалить после обновления до версии 2.29.0
if sys.version_info < (3, 11):
ssl_context = ssl.create_default_context()
else:
ssl_context = None
Тестирование
Тесты обеспечивают уверенность в корректности работы кода и защищают от регрессий при внесении изменений. Структура тестов отражает структуру тестируемого кода, что упрощает навигацию и поддержку.
Структура тестов
Тесты организуются в директории tests с поддиректориями unit для модульных тестов и integration для интеграционных. Каждый тестовый файл соответствует тестируемому модулю с префиксом test_. Каждый тестовый метод начинается с префикса test_ и описывает проверяемое поведение.
Код ITЗагрузка примера кода…
Принципы написания тестов
Каждый тест должен проверять одно конкретное поведение. Тест должен быть изолированным — не зависеть от состояния других тестов или внешних систем. Для внешних зависимостей используются моки и заглушки. Тест должен быть быстрым — выполняться за доли секунды. Медленные тесты выносятся в отдельную категорию интеграционных тестов.
Имена тестовых методов описывают проверяемое поведение в формате "действие_условие_результат":
def test_calculate_total_with_discount_applied():
pass
def test_validate_email_rejects_invalid_format():
pass
def test_process_order_creates_audit_log_entry():
pass
Тесты покрывают не только сценарии успешного выполнения, но и обработку ошибок, граничные условия, пустые входные данные. Для каждого публичного метода должны существовать тесты, проверяющие его поведение во всех ожидаемых сценариях.
Инструменты автоматизации и проверки кода
Автоматизация проверки кода обеспечивает соблюдение стандартов без ручного контроля. Инструменты интегрируются в процесс разработки и предотвращают попадание некачественного кода в основную ветку.
Статический анализ
Инструменты статического анализа проверяют код на соответствие стилю, выявляют потенциальные ошибки и проблемные конструкции. Для Python используются:
- ruff — быстрый линтер и форматировщик, заменяющий комбинацию flake8, isort и pyupgrade
- mypy — статический анализатор типов, проверяющий корректность аннотаций
- bandit — анализатор безопасности, выявляющий уязвимости в коде
Конфигурация инструментов размещается в файле pyproject.toml:
Код ITЗагрузка примера кода…
Автоматическое форматирование
Форматирование кода выполняется автоматически инструментом Black. Black применяет единый стиль без настроек, что устраняет споры о форматировании. Интеграция с прекоммит-хуками обеспечивает применение форматирования перед каждым коммитом.
Конфигурация Black в pyproject.toml:
[tool.black]
line-length = 88
target-version = ["py310"]
Типизация
Аннотации типов повышают надёжность кода и улучшают поддержку в редакторах. Все публичные функции и методы должны содержать аннотации параметров и возвращаемого значения. Для сложных типов используются возможности модуля typing:
from typing import List, Dict, Optional, Union, Callable
from datetime import datetime
def process_orders(
orders: List[Dict[str, Union[str, float, int]]],
callback: Optional[Callable[[str], None]] = None
) -> Dict[str, Union[int, float, datetime]]:
pass
Для Python 3.9 и новее предпочтительны встроенные обобщённые типы вместо импортов из typing:
# Python 3.9+
def get_users() -> list[dict[str, str]]:
pass
# Вместо (для более старых версий)
from typing import List, Dict
def get_users() -> List[Dict[str, str]]:
pass
Проверка типов выполняется инструментом mypy как часть процесса сборки. Ошибки типизации рассматриваются как критические и должны исправляться до слияния изменений.
Практические рекомендации для повседневной разработки
Разработка программного обеспечения включает множество рутинных операций, для которых существуют проверенные подходы. Следование этим подходам экономит время и снижает количество ошибок.
Работа с конфигурацией
Конфигурационные параметры извлекаются из переменных окружения с помощью библиотеки pydantic-settings или аналогичных решений. Все параметры имеют значения по умолчанию для локальной разработки. Конфигурация валидируется при запуске приложения.
Код ITЗагрузка примера кода…
Никогда не коммитьте файл .env с секретными данными в систему контроля версий. В репозитории размещается только пример .env.example с описанием необходимых переменных.
Работа с внешними сервисами
Все обращения к внешним сервисам выполняются через абстракции — интерфейсы или базовые классы. Это позволяет заменять реализации для тестирования и упрощает миграцию на другие сервисы.
Код ITЗагрузка примера кода…
В продакшене используется SESMailService, в разработке и тестах — DebugEmailService. Переключение выполняется через конфигурацию без изменения клиентского кода.
Логирование
Логирование выполняется через стандартный модуль logging с настройкой форматтеров и хэндлеров. Каждый модуль получает собственный логгер с именем модуля. Уровень логирования устанавливается централизованно через конфигурацию.
Код ITЗагрузка примера кода…
Для структурированного логирования в продакшене используется формат JSON с дополнительными полями контекста — идентификаторами запроса, пользователя, транзакции. Это упрощает анализ логов с помощью инструментов вроде ELK Stack или Splunk.
Управление состоянием и мутабельностью
Предпочитайте неизменяемые структуры данных там, где это возможно. Для представления данных используйте dataclass с параметром frozen=True или NamedTuple. Неизменяемость упрощает рассуждение о коде, предотвращает побочные эффекты и упрощает тестирование.
Код ITЗагрузка примера кода…
При необходимости изменения состояния создавайте новый объект с обновлёнными значениями вместо модификации существующего. Для сложных объектов используйте методы-помощники, возвращающие копии с изменениями.