Лучшие практики написания кода на Python (2026)

1. Общие принципы чистого кода на Python
При разработке на Python следует руководствоваться философией The Zen of Python (PEP 20) – набором принципов, подчеркивающих важность простоты и ясности кода. Например, “Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex.” («Красивое лучше, чем некрасивое. Явное лучше неявного. Простое лучше сложного.»). Один из ключевых афоризмов Zen of Python гласит: “Readability counts.” («Читаемость кода имеет значение»), и действительно, код читается гораздо чаще, чем пишется. Поэтому чистый код – это код, который легко читать и поддерживать.
Основные принципы чистого кода на Python включают:
- Простота и ясность: Стремитесь к простым решениям и избегайте излишней сложности. Код должен быть понятен другим разработчикам без длительных разъяснений. Если реализацию сложно объяснить – это плохой признак, а если легко – скорее всего, идея хороша.
- Единичность ответственности: Каждая функция или класс должны решать одну конкретную задачу. Разбиение сложной задачи на меньшие части делает код чище и упрощает тестирование.
- Не повторяйся (DRY): Избегайте дублирования кода. Выделяйте повторяющуюся логику в функции или утилитарные модули. Это снижает количество мест, которые нужно менять при правках, и уменьшает риск ошибок.
- Максимально используйте возможности Python: Пишите «питонообразный» (pythonic) код, пользуясь идиоматичными конструкциями. Например, вместо громоздких циклов и условных операторов используйте list/dict comprehensions, встроенные функции (
sum,max,enumerateи др.), конструкциюwithдля работы с ресурсами, генераторы для отложенных вычислений и т.д. – всё это делает код чище и зачастую эффективнее. - Следуйте руководящим принципам языка: Руководство по стилю Python (PEP 8) и философия Zen of Python являются ориентирами для принятия решений в неоднозначных случаях. Когда есть несколько способов реализовать функциональность, обычно предпочтителен тот, что обеспечивает наибольшую ясность и простоту (в соответствии с девизом «Должен существовать один – и желательно только один – очевидный способ сделать это»)
Pythonl – с помощью понятных картинок и коротких видео авторы объясняют сложные концепции и учат профессиональному подходу в разработке.
🔝 А здесь мы собрали целый кладезь полезных Python ресурсов для прокачки.
2. Современные рекомендации по стилю (PEP 8 и актуальные изменения)
Стиль кода в Python преимущественно регламентируется документом PEP 8 – Style Guide for Python Code. PEP 8 устанавливает единые соглашения, благодаря которым код разных разработчиков выглядит единообразно и легко читается. Ниже перечислены основные современные рекомендации по стилю (с учётом PEP 8 и нововведений последних лет):
- Отступы: Используйте отступ в 4 пробела на каждый уровень вложенности кода. Табулирование не рекомендуется – PEP 8 прямо указывает, что предпочтительны пробелы, а табы допускаются только для совместимости со старым кодом. Никогда не смешивайте табы и пробелы в одном проекте.
- Длина строк: Старайтесь ограничивать длину строк приблизительно 79 символами. Это облегчает чтение кода в текстовых редакторах и инструментах обзора кода. Многие современные проекты допускают чуть более длинные строки (например, 88 или 100 символов), однако превышение 79 символов должно быть обоснованным. Если выражение слишком длинное, разбейте его на несколько строк с помощью обратного слэша или скобок. PEP 8 также дает рекомендации по разрыву строк – например, производить разрыв перед бинарным оператором или использовать висячие отступы для улучшения читаемости.
- Пробелы в выражениях: Соблюдайте аккуратность с пробелами. Перед запятой и перед точкой с запятой пробел не ставится, а после – ставится. Вокруг операторов присваивания и других бинарных операторов (например,
=,+,-,==и т.д.) принято ставить пробелы с обеих сторон для отделения их от операндов. Исключение – ситуации, когда сниженная плотность кода повышает его понятность (например, параметры функции с default-значениями, о чём ниже). Не вставляйте лишних пробелов там, где они не нужны: внутри скобок, перед запятой или двоеточием, перед открывающей скобкой вызываемой функции и т.п. – эти “лишние” пробелы не улучшают, а только сбивают чтение кода. - Размещение скобок и запятых: Для длинных литералов контейнеров (списков, словарей) рекомендуется ставить запятую после каждого элемента, включая последний – тогда при добавлении нового элемента в конец дифф будет минимальным. Закрывающую скобку структур данных или вызова функции обычно размещают на новой строке, выровненной под начало выражения.
- Комментирование: PEP 8 рекомендует писать комментарии на том же языке, на котором код будут читать разработчики (чаще всего – на английском). Комментарии не должны противоречить коду и должны оперативно обновляться при изменении кода. Отдавайте предпочтение полноценным docstring (о них ниже) для описания модулей, функций и классов, а не написанию объемных комментариев. Однострочные комментарии внутри функции должны быть короткими и по делу, без излишних очевидных пояснений (например, не нужно писать
# increment xрядом сx = x + 1). - Автоматическое форматирование кода: В 2020-х годах широкое распространение получили автоформатеры кода. Например, инструмент Black позиционируется как “непримиримый форматер” и автоматически приводит код к единому стилю, соответствующему PEP 8. Использование автоформатеров стало лучшей практикой: при настройке Black (или аналогичных инструментов, таких как yapf или autopep8) на вашем проекте вы делегируете выравнивание отступов, разбиение длинных строк и расстановку пробелов инструменту. Это избавляет команду от споров о стиле и позволяет сосредоточиться на логике кода. В 2026 году такой подход является нормой для большинства профессиональных проектов – достаточно настроить форматирование как шаг в CI/CD или интегрировать в IDE, и весь код будет автоматически соответствовать принятому стилю.
Важно понимать, что PEP 8 – это не свод жёстких правил, а руководство. В самом начале PEP 8 отмечено: “Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is the most important.” («Соблюдение этого стиля важно. Ещё важнее – консистентность стиля внутри проекта. А самая важная – внутри одного модуля или функции.»). То есть, наиболее важно сохранять единообразие стиля внутри вашего кода. Иногда отклонение от рекомендаций допустимо, если оно улучшает читаемость или необходимо для поддержки существующего кода – “When in doubt, use your best judgment” (в сомнительной ситуации полагайтесь на здравый смысл).
3. Именование переменных, функций и классов
Грамотный выбор имён – залог понятного кода. PEP 8 устанавливает чёткие соглашения об именованиях в Python:
- Имена переменных и функций пишутся в нижнем регистре и используются слова, разделённые символом подчёркивания (snake_case) для улучшения читабельности: например,
total_sum,process_data(). Функции обычно называют глаголами или глагольными фразами, описывающими действие (например,calculate_total,send_email). Переменные – существительными или короткими фразами, отражающими содержимое. - Имена классов должны следовать стилю CapWords (также называемому CamelCase – каждое слово с заглавной буквы, без разделителей). Например,
DataParser,UserAccount. Для исключений принято добавлять окончание"Error"к имени класса (например,ValueError), если класс действительно представляет ошибку. - Имена констант (глобальные переменные, значения настроек) записываются заглавными буквами с подчёркиваниями между словами. Например,
MAX_CONNECTIONS,DEFAULT_TIMEOUT. Константами считаются значения, которые не должны изменяться в ходе работы программы. - Приватные переменные и методы: в Python нет строгого модификатора доступа, но по соглашению имена, начинающиеся с одного подчёркивания
_, считаются «не публичными», внутренними для модуля или класса. Интерпретатор не запрещает доступ к таким атрибутам извне, но разработчики понимают, что они не предназначены для внешнего использования. Wildcard-импорт (from module import *) по умолчанию не импортирует имена, начинающиеся с подчёркивания. Если нужно явно указать публичный API модуля, определяют список__all__внутри модуля. - Двойное подчёркивание в начале имени (
__var) запускает механизм name mangling – к имени автоматически добавляется имя класса, что служит для предотвращения случайных конфликтов имен при наследовании. Однако не рекомендуется изобретать “магические” имена с обоих сторон двойным подчёркиванием (__like_this__) – такие имена зарезервированы для специальных методов и атрибутов Python (например,__init__,__len__и т.д.) и внедрять свои не следует. - Аргументы методов и функций: Первый аргумент метода экземпляра класса обязательно называется
self(что обозначает сам объект). Для методов класса (объявленных с декоратором@classmethod) первым аргументом должен бытьcls. Несоблюдение этого соглашения будет как минимум озадачивать других разработчиков, а отсутствиеselfв методе приведёт к ошибке. Также, если имя аргумента совпадает с ключевым словом Python, добавляется подчёркивание на конце (class_,lambda_), вместо искажения слова.
Выбирайте имена, отражающие суть. Код, в котором переменные называются a, b, foo, data – затрудняет понимание. Лучше потратить время и подобрать выразительное имя, чем снабжать каждую строчку комментариями с объяснениями. Например, вместо:
# Плохой пример
d = {} # словарь ключей и значений параметров
for k, v in items:
if len(v) > 5:
d[k] = v
можно написать:
# Хороший пример
params = {}
for key, value in items:
if len(value) > 5:
params[key] = value
Здесь вместо непонятного d используется params (коротко от parameters, понятно, что это словарь параметров). Имена key и value вместо k и v делают цикл самодокументируемым – без комментария ясно, что происходит.
Следование соглашениям об именах облегчает чтение кода без дополнительных комментариев. Когда встречаешь класс OrderProcessor или функцию send_email(), ее предназначение понятно сразу. Стиль именования в Python стал стандартом де-факто: он поддерживается большинством линтеров и проверяется автоматически. Например, PEP 8 указывает, что нельзя использовать имена переменных l, O, I (маленькая эль, большая о, большая и) – их легко спутать с цифрами 1 и 0. Такие нюансы тоже следует помнить.
4. Организация структуры проекта
Правильная структура проекта облегчает навигацию по коду и его поддержку. По мере роста проекта из одного файла в набор модулей и пакетов нужно организовывать код так, чтобы было чётко понятно, где что находится. Современные best practices предполагают следующие подходы к структуре Python-проекта:
- Выделение пакета с исходным кодом: Код приложения или библиотеки обычно выделяют в отдельный пакет (директорию) внутри корневой папки проекта. Распространены два подхода:
- Flat layout – пакет размещается на верхнем уровне. Например, если проект называется myproject, структура будет:
myproject/ myproject/ <- пакет с кодом (модули и подмодули) tests/ <- директория с тестами README.md pyproject.toml ...При таком подходе название корневой директории и внутреннего пакета совпадают. - Src layout – исходники лежат в папке
src/. Например:myproject/ src/ myproject/ <- пакет с кодом __init__.py module1.py ... tests/ pyproject.toml README.mdТакой подход в последние годы набирает популярность, так как предотвращает случайный импорт “локальной” версии пакета вместо установленной (когда запускаются тесты или скрипты). Многие шаблоны проектов (включая официальные рекомендации PyPA) используют структуру с папкойsrc.
- Flat layout – пакет размещается на верхнем уровне. Например, если проект называется myproject, структура будет:
- Пакетирование и модули: Разбивайте код на модули по функциональному признаку. Не складывайте весь код в один файл – лучше создать пакет с несколькими модулями, каждый из которых отвечает за свою часть логики (например,
api.py,models.py,utils.pyи т.п.), либо вложенные подпакеты для логически обособленных частей. Избегайте ситуаций, когда в одном модуле оказываются десятки несвязанных функций – это затрудняет поддержку. Вместо этого старайтесь проектировать структуру из небольших модулей, группируя схожую функциональность вместе. Это повышает переиспользуемость кода и делает зависимости явными. - Отделение кода и тестов: Храните автоматические тесты отдельно от основного кода, обычно в директории
tests/на верхнем уровне проекта. Внутриtests/можете организовать подпапки, отражающие структуру пакета (например,tests/unit/test_module1.py,tests/integration/test_featureX.py). Такой подход не только упрощает запуск тестов, но и исключает попадание тестовых файлов в финальную сборку приложения. - Файлы конфигурации и метаданные: В корне проекта обычно располагаются файлы:
README.md(описание проекта, примеры использования),LICENSE(лицензия),pyproject.toml(метаданные проекта: зависимости, конфигурация инструментов сборки и линтеров), а также возможныrequirements.txt(список зависимостей для pip, если не используется pyproject), настройки CI (.github/workflowsили.gitlab-ci.yml) и т.д. Pyproject.toml в 2026 году стал стандартным единым файлом конфигурации для описания пакета и инструментов разработки (согласно PEP 518/517). - Логическое разделение слоёв приложения: В крупных приложениях разделяйте «слои» – например, слой бизнес-логики, слой доступа к данным, слой веб-интерфейса или CLI – по разным подпакетам. Это делает архитектуру более явной. Пример:
myproject/core/(основная логика),myproject/db/(взаимодействие с БД),myproject/api/(REST API, если есть),myproject/gui/(если присутствует графический интерфейс), и т.д. Такое разделение по каталожной структуре помогает избежать переплетения кода разных уровней и упрощает навигацию. - Имена модулей и пакетов: PEP 8 рекомендует называть файлы модулей короткими именами в нижнем регистре. В имени модуля могут использоваться подчёркивания, если так улучшается читаемость (например,
socket_server.py), но в целом предпочтительны короткие имена без них. Имена пакетов – тоже в нижнем регистре, по возможности без подчёркиваний. Избегайте в именах модулей и пакетов дефисов, пробелов и других символов, кроме букво-цифр и подчёркивания.
Хорошо организованный проект сразу виден при взгляде на структуру каталогов. Например:
myproject/
├── pyproject.toml
├── README.md
├── src/
│ └── myproject/
│ ├── __init__.py
│ ├── __main__.py # Точка входа (например, if __name__ == "__main__")
│ ├── data_models.py
│ ├── processing.py
│ └── utils.py
└── tests/
├── __init__.py
├── test_processing.py
└── test_utils.py
В таком проекте сразу видно, где основной код (src/myproject/), где тесты, где настройки. Плохой пример – когда в корне перемешаны файлы с кодом, данными, тестами и документами, без ясного разделения. Структурированность облегчает понимание: где точка входа, где основные компоненты, как связаны тесты и код.
Кроме того, правильная структура помогает избежать ошибок с импортами. Например, при наличии отдельной папки src/ разработчики всегда импортируют пакет как import myproject (указывая PYTHONPATH на src), вместо неявного импорта из текущей директории, что предотвращает конфликты имен файлов с именами стандартных модулей.
Итог: инвестируйте время в организацию проекта с самого начала. Как отмечает RealPython, «предсказуемый layout облегчает понимание, как части проекта сочетаются друг с другом, и позволяет быстро принимать решения, куда добавлять новый код или ресурсы». Хорошая структура – это фундамент, на котором строится остальной код.
5. Документирование кода и использование docstrings
Документация – неотъемлемая часть качественного кода. В Python основной способ документирования – это docstring (строка документации), которая пишется в виде строкового литерала сразу под определением модуля, класса или функции. PEP 257 определяет соглашения по оформлению docstring. Краткие рекомендации:
- Пишите docstring для всех публичных модулей, функций, классов и методов. Исключение можно сделать для негласных (внутренних) утилит, но даже для них полезно хотя бы кратко указать назначение комментариями.
- Формат docstring: используйте тройные кавычки
""" ... """для оформления, даже если строка умещается в одну линию. Для многострочных docstring первая строка должна быть кратким обобщающим предложением, затем пустая строка, после – более развернутое описание при необходимости. Первая строка обычно формулируется как повелительное наклонение, описывающее что делает функция (например,"Calculate sum of all elements."), а не что она из себя представляет. В конце многострочной docstring закрывающие кавычки ставятся на отдельной строке. - Однострочные docstring: если документируемая вещь настолько проста, что укладывается в одно предложение, можно написать docstring в одну строку (начав и закончив тройными кавычками в одной строке). В таком случае не ставится дополнительный перенос строки ни перед, ни после строки.
- Содержание docstring: Опишите назначение функции или класса, а не пересказывайте имя. Хорошая docstring отвечает на вопрос «что делает этот код и как его использовать?». Для функций стоит указывать, что возвращается (если не очевидно). Не нужно дублировать сигнатуру функции в docstring (типы и имена параметров можно узнать через аннотации и introspection). Лучше раскрыть нюансы: единицы измерения, допустимые диапазоны значений, побочные эффекты, исключения, которые могут быть брошены, и т.д. Например, вместо плохой docstring
"""function(a, b) -> list"""напишите"""Combine lists a and b and return a new list."""– то есть осмысленное описание действия. - Документирование классов: В docstring класса обычно указывают предназначение класса и описывают важные методы или использование. PEP 257 рекомендует после docstring класса оставлять пустую строку перед первой методой, чтобы визуально отделить докстринг от тела класса.
- Форматы docstring: Строгого стандарта форматирования содержимого нет, но сообщество выработало несколько стилевых шаблонов:
- reStructuredText (reST) – используется в Sphinx и многих проектах. Позволяет разметить разделы
:param name: ...,:return: ...и т.д. для генерации красивой документации. - Google style docstrings – более лаконичный стиль, где описания параметров оформляются как секции с отступами.
- NumPy style – похож на Google, но с секциями Parameters, Returns, Examples и т.д.
- reStructuredText (reST) – используется в Sphinx и многих проектах. Позволяет разметить разделы
- Доступ к docstring: Помните, что строки документации доступны во время выполнения через атрибут
__doc__объекта или функциюhelp(). Это удобно: например, набрав в интерактивном режимеhelp(my_function), вы увидите ее docstring. Поэтому пишите docstring так, чтобы она была понятна и полезна даже без просмотра кода функции. - Комментарии vs docstring: Обычные комментарии (начинающиеся с
#) не попадают вhelp()и служат, скорее, для пометок внутри реализации. Используйте их для пояснения сложных участков кода или временных решений. Но для внешнего описания интерфейсов (API) всегда предпочтительны docstring, т.к. они интегрируются с инструментами документации и IDE.
Пример оформления docstring для функции:
def scale_values(data: list[float], factor: float) -> list[float]:
"""Вернуть новый список, в котором каждое число из data умножено на factor.
Аргументы:
data (list of float): входной список чисел.
factor (float): множитель для масштабирования.
Returns:
list of float: новый список, содержащий результаты умножения.
"""
return [x * factor for x in data]
В этом примере docstring многострочный: первое предложение – сжатое описание, далее после пустой строки разъясняются параметры и результат. Обратите внимание, что благодаря аннотациям типов (о них далее) мы указали типы параметров и возвращаемого значения в сигнатуре, поэтому в скобках после названий параметров и Returns это дублируется только для ясности. Многие инструменты (например, IDE или Sphinx) могут автоматически извлечь эту информацию.
Документируя код, вы облегчаете жизнь и коллегам, и самому себе в будущем. Хорошо написанная документация делает использование вашего кода интуитивным и снижает количество вопросов «что делает эта функция?». Согласно PEP 20, “Explicit is better than implicit” – лучше явно объяснить поведение, чем полагаться на догадки.
6. Написание тестов и современные фреймворки (например, Pytest)
Тестирование – ключевой аспект качественной разработки. Написание автоматических тестов обеспечивает уверенность в том, что код работает правильно, и позволяет вносить изменения (рефакторить) без страха сломать существующий функционал. К 2026 году сообщество Python выработало следующие лучшие практики в тестировании:
- Выбор фреймворка: В стандартной библиотеке Python есть модуль
unittest(он жеPyUnit), но наиболее популярным и удобным фреймворком стал Pytest. Pytest ценится за лаконичность и простоту: вместо создания классов тестов и использования специальных методов, как вunittest, вы пишете обычные функции, а проверки делаете простымиassertвыражениями. По состоянию на конец 2024 года вышла версия Pytest 9.0, и он фактически стал стандартом де-факто для тестирования Python-проектов. Его популярность обусловлена мощной экосистемой плагинов (для тестирования БД, asyncio, mock-объектов и пр.) и более читаемыми сообщениями об ошибках. - Организация тестов: Как упоминалось в разделе о структуре проекта, держите тесты в отдельной папке (обычно
tests). Pytest по умолчанию ищет файлы, начинающиеся сtest_, и функции внутри них, названныеtest_*. Распространённый шаблон – отражать структуру основного пакета: например, для модуляmyproject/utils.pyсделать файлtests/test_utils.pyс тестами его функций. Тесты должны быть независимы от окружения: не полагаться на внешние файлы или глобальное состояние (или, если требуется, явно настраивать окружение в коде теста). - Покрытие тестами ключевой логики: Стремитесь писать тесты для каждой функциональности, особенно для краевых случаев. Хорошая практика – применять методику TDD (Test-Driven Development), когда вы сначала пишете тесты на ожидаемое поведение, а затем реализацию. Это помогает четче сформулировать требования к коду.
- Принципы написания тестов: Используйте понятные имена тестов, описывающие поведение. Существует шаблон AAA (Arrange-Act-Assert) – подготовка данных, выполнение действия, проверка результата – придерживайтесь его структуры в каждом тесте для ясности. Например:
def test_division_with_positive_numbers(): # Arrange calc = Calculator() dividend = 10 divisor = 2 # Act result = calc.divide(dividend, divisor) # Assert assert result == 5.0Такой подход облегчает чтение и понимание того, что проверяет данный тест. - Изоляция тестов: Каждый тест должен быть независимым. Ни один тест не должен зависеть от выполнения другого. Иначе появятся flaky tests – «мигающие» тесты, которые проходят или падают в зависимости от порядка запуска. Чтобы обеспечить изоляцию, используйте fixtures (фикстуры) для подготовки и очистки окружения для теста. В Pytest фикстуры объявляются через декоратор
@pytest.fixtureи могут возвращать подготовленные объекты, а после выполнения теста выполнять teardown (уборку) при помощи конструкцииyield. Фикстуры делают код тестов чище и убирают дублирование при настройке повторяющихся состояний (например, подключение к БД для группы тестов). - Моки и стабы: В тестах, особенно модульных, может потребоваться изолировать код от внешних зависимостей – сетевых запросов, базы данных, файловой системы. Для этого применяют mock-объекты (в Python есть библиотека
unittest.mock). Pytest облегчает работу с моками через плагины (например,pytest-mock) и позволяет быстро заменять части системы заглушками. - Покрытие и качество тестов: Используйте инструменты измерения покрытия кода тестами (coverage). Показатель покрытия (%) не самоцель, но он помогает найти неохваченные тестами участки. Старайтесь покрыть как минимум критическую бизнес-логику. Также обращайте внимание на качество проверок: тест должен не просто вызывать функцию, но и проверять, что она вернула правильный результат или вызвала ожидаемые побочные эффекты.
- Интеграция в процесс разработки: Настройте запуск тестов при каждом коммите (в идеале – через CI). На локальной машине разработчики тоже должны регулярно прогонять тесты. Инструменты наподобие
pytest --maxfail=1 -vускоряют выявление ошибок, останавливаясь на первом падении. Следите, чтобы в репозитории не оставалось непройденных тестов – красные сборки CI недопустимы.
Стоит отметить, что тесты сами по себе служат живой документацией кода. Глядя на хорошо написанный тест, можно понять, как должен работать тот или иной компонент. Например, тесты могут показать пример использования API или ожидания к формату данных. Поэтому важно писать тесты не впопыхах, а также поддерживать их в актуальном состоянии.
Подводя итог: покрытие кода тестами – лучший друг чистого кода. В 2026 году зрелые проекты имеют обширные тестовые наборы, и разработчики рассматривают написание тестов как неотъемлемую часть работы, а не как опциональное упражнение. Pytest и сопутствующие инструменты делают этот процесс достаточно удобным, чтобы автоматические тесты стали стандартом индустрии.
7. Использование аннотаций типов (Type Hints)
Аннотации типов – пожалуй, самое заметное изменение в стиле Python-кода за последнее десятилетие. Если раньше Python был строго динамически типизированным языком, то начиная с версии 3.5 (2015 год) в него постепенно были введены средства статической типизации в виде подсказок типов (PEP 484). К 2026 году применение type hints стало повсеместным и крайне рекомендуемым:
- Зачем нужны type hints: Аннотации типов повышают читаемость и надежность кода. Указав типы параметров и возвращаемых значений функций, вы тем самым документируете контракт функции – что она ожидает на вход и что выдаёт. Это облегчает понимание кода (особенно новым участникам проекта) и служит дополнительной документацией. Кроме того, статические анализаторы и IDE способны ловить целый класс ошибок ещё до запуска программы: несоответствие типов, отсутствие необходимых атрибутов, неверное использование API и т.п. В большом кодовой базе наличие корректных type hints значительно упрощает рефакторинг – при изменении типа данных вы сразу видите, где код не соответствует новым ожиданиям.
- Широкое принятие в сообществе: Если в конце 2010-х многие относились к аннотациям скептически, то сейчас ситуация иная. Согласно опросу 2025 года, 86% Python-разработчиков «всегда или часто» используют type hints в своём коде. Type hints превратились в неотъемлемую часть разработки для большинства инженеров. Это поддерживается и эволюцией языка: практически каждая новая версия Python добавляет улучшения в систему типов (например, объединение типов через оператор
|в Python 3.10, введениеTypeAliasиSelfв 3.11, улучшение работы с Generics к 3.12 и т.д.). - Как использовать аннотации: Синтаксис аннотаций простой – двоеточие после имени параметра и указание типа, а для возвращаемого значения –
-> Typeперед двоеточием объявления функции. Например:def greet(name: str) -> str: return "Hello, " + nameЗдесь указано, что функция принимает строку и возвращает строку. Аннотации могут описывать сложные типы:list[int],dict[str, tuple[int, int]]и т.д. (Начиная с Python 3.9 можно использовать родные типы, а неtyping.List). Также можно аннотировать переменные: например,count: int = 0указывает тип переменнойcount. - PEP 8 о стилях аннотаций: PEP 8 адаптирован под аннотации типов. Основные правила оформления: не ставьте пробел перед двоеточием при аннотации, но ставьте пробел после двоеточия. В объявлении функции вокруг
->должны стоять пробелы с обеих сторон. Например, правильно:def func(x: int) -> None: ...(пробелы вокруг->), неправильно:def func(x:int)->None:. Если у параметра есть значение по умолчанию и аннотация, пробелы ставятся вокруг=(например,x: List[int] = None), а вот у неаннотированного параметра пробелы вокруг=не ставятся (y=10внутри сигнатуры). - Проверка типов и инструменты: Аннотации сами по себе никак не влияют на выполнение кода (интерпретатор их игнорирует), но их задействуют статические анализаторы – такие как mypy, Pyright, Pyre и др. Эти инструменты анализируют ваш код, не выполняя его, и выдают предупреждения, если типы не сходятся. Например, mypy сообщит об ошибке, если вы пытаетесь передать строку в функцию, ожидающую
int. Интеграция mypy или Pyright в процесс CI помогает удерживать код в корректном состоянии. Кроме того, IDE (PyCharm, VS Code, etc.) используя аннотации, дают более умные подсказки и автодополнение кода. - Runtime-аспекты: Несмотря на статическую природу, есть библиотеки, которые могут использовать аннотации во время выполнения для валидации. Например, Pydantic или FastAPI читают аннотации, чтобы автоматически проверять типы входных данных (например, JSON в запросах) и сериализовать ответы. Библиотека beartype может декорировать функции и выполнять проверку типов при каждом вызове в рантайме. Однако повсеместно в код вставлять runtime-чекеры не требуется – достаточно договориться об использовании статических проверок на этапе разработки.
- Советы по введению аннотаций: Если вы работаете с легаси-кодом без аннотаций, можно внедрять их постепенно. Начните с наиболее важных модулей или новых функций. Mypy позволяет постепенно настраивать строгость (например, опция strict optional или ignore missing imports). Можно также использовать stub-файлы
.pyiдля сторонних библиотек без аннотаций, либо устанавливать пакеты с типами из репозитория typeshed (они имеют названия видаtypes-имя_библиотеки). - Новые возможности типов к 2026 году: Python-типизация развивается: появились TypeAlias (именованные алиасы типов), TypedDict (для описания словарей с фиксированными полями), Protocol (структурная типизация для классов), литеральные типы, поддержка обобщённых типов контейнеров (Generic Types) без наследования от
typing.Genericи многое другое. Эти возможности позволяют описать типы более точно. Например, с Python 3.10 можно писатьint | NoneвместоOptional[int], что улучшает читаемость аннотаций. - Документирование vs аннотации: Когда в коде присутствуют аннотации типов, зачастую нет необходимости дублировать информацию о типах в docstring. Это позволяет сосредоточить документацию на описании логики и намерения функции, а механическая информация о типах будет доступна из сигнатуры. Разработчики отмечают, что type hints делают код самодокументируемым в большой степени.
В итоге, использование аннотаций типов стало лучшей практикой. В новом коде 2026 года считается хорошим тоном аннотировать все публичные функции и методы, а также сложные структуры данных. Исключения могут составлять очень простые вспомогательные функции (типа get_key при очевидных типах), но и их часто аннотируют – “на всякий случай”. Как говорится, “чем явно указанный тип хуже, чем неявный?”. Опыт показывает, что правильно расставленные type hints предотвращают множество багов и облегчают поддержку. Недаром Python-сообщество теперь массово их применяет, считая, что “Python’s type hinting system has become a core part of development” – неотъемлемая часть разработки.
8. Советы по производительности и читаемости
Грамотный код должен быть не только чистым, но и эффективным. Однако в Python (как и в любом высокоуровневом языке) всегда приходится искать баланс между производительностью и читаемостью. Несколько рекомендаций, актуальных на 2026 год:
- Не жертвуйте читаемостью без крайней необходимости: Преждевременная оптимизация – зло. Этот известный принцип остается верным и в Python. Сначала напишите корректный и понятный код, и только если профилирование покажет узкие места по скорости, думайте об оптимизации. Практичность важнее чистой производительности – как гласит Zen of Python, “Although practicality beats purity.” (Практичность превыше чистоты). Читаемый код проще отлаживать и сопровождать. Оптимизировать же стоит только действительно критичные места, причём точечно.
- Используйте встроенные возможности языка и стандартной библиотеки: Очень часто Python предоставляет готовое, оптимизированное решение, которое работает быстрее самописного. Например:
- Используйте генераторы и списковые включения вместо вручную накопления списков в цикле – они не только короче, но и работают быстрее за счёт оптимизаций в Си.
- Применяйте функции
sum(),min(),max(),sorted()и т.п. вместо написания собственных циклов подсчёта – эти функции реализованы на C и обычно более эффективны. - Для проверки вхождения элемента в коллекцию используйте наборы (
set) или словари (операция in для них – O(1) амортизированно) вместо списков (O(n)). Если нужно часто проверять принадлежность, преобразуйте список в set один раз. - Сравнивайте строки на префикс/суффикс методами
str.startswith()/str.endswith(), а не через срезы – это и читается лучше, и менее ошибкоопасно. - Проверяйте тип объекта через
isinstance(obj, SomeClass)вместо сравнения типа напрямую (type(obj) is SomeClass) – первый способ учитывает наследование и является более гибким.
- Следите за алгоритмической сложностью: Python хоть и не компилируется в машинный код, но плохая алгоритмика ощутимо сказывается. Например, вложенные циклы на больших входных данных могут быть узким местом. Используйте профилировщики (как встроенный
cProfileили инструменты вроде pyinstrument) чтобы найти, где программа тратит время, и оптимизируйте именно эти фрагменты. - Работа со строками: Конкатенация строк в цикле через
+– известный анти-паттерн, т.к. в CPython она работает эффективно только благодаря некоторым внутренним оптимизациям, которые не гарантированы на других реализациях Python. Вместо накопления строк делайте''.join(list_of_strings)– этот приём обеспечивает линейную сложность и зачастую быстрее, особенно на альтернативных реализациях Python. - Память и большие данные: Если вы работаете с крупными объёмами данных, обращайте внимание на память. Например, при чтении большого файла не загружайте всё содержимое в список строк, а итерируйтесь по файлу построчно. Используйте генераторы для ленивой обработки (вычисление «на лету» без хранения всего результата в памяти).
- Параллелизм и асинхронность для производительности: В 2026 году уже стабилизировалась экосистема асинхронного Python (asyncio). Если ваша задача связана с большим числом IO-операций (запросы в сеть, дисковые операции), рассмотрите асинхронное выполнение –
async/awaitпозволяют эффективно использовать время ожидания. Для CPU-связанных задач помните про ограничения GIL: может быть полезно распределить вычисления по процессам (модульmultiprocessing) или использовать библиотеки на C (numpy, etc.), чтобы задействовать все ядра. - Читаемость кода и производительность: Часто можно улучшить и читаемость, и производительность одновременно, просто выбрав удачное решение. Например, вместо:
# Плохо: явный цикл, ручная проверка result = [] for x in data: if condition(x): result.append(x * 2)можно написать:# Хорошо: списковое включение, фильтрация по условию result = [x * 2 for x in data if condition(x)]Эта одна строка выполняет ту же логику. Она короче и понятнее, а за счёт реализации в C – зачастую и быстрее. Однако важно не злоупотреблять выражениями генераторов: если логика слишком сложная или вложенная, лучше вернуть к обычным циклам с комментариями, иначе читаемость пострадает. Как говорит один из принципов PEP 20, “If the implementation is hard to explain, it’s a bad idea.” («Если реализацию сложно объяснить – идея плоха»). - Профилируйте критичный код: Современные советы сводятся к тому, чтобы измерять, а не гадать. Интуиция по производительности может подвести. Используйте профайлеры и бенчмаркинг (модуль
timeit) для критичных участков. Возможно, “узким местом” окажется не то, что вы думали. Оптимизировав код на основе фактов, вы избежите преждевременной оптимизации там, где в ней не было нужды, и сосредоточитесь на реальных проблемах. - Помните о PEP 8 Programming Recommendations: В конце PEP 8 есть раздел рекомендаций, многие из которых одновременно улучшают и качество, и производительность кода. Например:
- Не используйте проверку типа через
==для одиночных экземпляровNone,True,False– всегда сравнивайте их черезisилиis not. Это быстрее и отражает намерения. - Избегайте пустых
except:без указания исключения – это не про скорость, но про надёжность: такие блоки ловят все исключения, включая SystemExit/KeyboardInterrupt, что затрудняет прервать программу и может скрыть баги. Указывайте конкретные исключения, которые хотите перехватить. - Используйте менеджеры контекста (
with) для работы с ресурсами (файлами, соединениями) – это гарантирует своевременное освобождение ресурсов даже при исключениях. Пример:with open('data.txt') as f: data = f.read()автоматически закроет файл, что и безопаснее, и читабельнее ручногоf = open(...); ...; f.close(). - Соблюдайте единообразие стилей возврата значений: если функция в одном месте возвращает объект, а в другом ничего (неявно
None), это ухудшает понимание. PEP 8 рекомендует явно возвращатьNone, если в некоторых ветках нет значения, – это минимизирует неожиданные типы возврата и упрощает аннотирование типов.
- Не используйте проверку типа через
В целом, оптимальный код в Python – это код «достаточно быстрый» для поставленной задачи и при этом максимально простой для понимания. В большинстве случаев читабельность важнее микроскопической экономии времени работы. Но когда вы точно знаете, что участок кода критичен (например, выполняется тысячи раз в секунду), – профилируйте, оптимизируйте, при необходимости переходите на более низкий уровень (написать расширение на Cython, использовать numpy для векторных вычислений, или даже вынести в С/С++).
И помните знаменитое правило: “Сначала сделай, чтобы работало, потом – чтобы понятно, и только потом – по возможности быстро”. В Python это особенно справедливо: зачастую «понятный» код благодаря усилиям разработчиков интерпретатора уже работает достаточно быстро, а вот «перехитривший» программистом интерпретатор код может оказаться и менее понятным, и вовсе не быстрее.
9. Частые ошибки и как их избегать
Даже опытные Python-разработчики иногда допускают типичные ошибки. Вот перечень наиболее распространённых “подводных камней” языка Python и советы, как их избежать:
- Изменяемые объекты как значения по умолчанию: Классическая ловушка – задавать изменяемые объекты (
list,dictи т.д.) в качестве значений параметров по умолчанию функции. Например,def add_item(item, lst=[]): .... В Python значение по умолчанию вычисляется один раз – в момент определения функции, а не при каждом вызове. Поэтому все вызовы без явного аргументаlstбудут разделять один и тот же список!. Это приводит к неожиданному поведению: первый вызовadd_item(5)создаст список[5], а второй вызовadd_item(7)без аргумента добавит 7 в тот же самый список, получим[5, 7]вместо ожидаемого[7]. Решение: использоватьNoneкак сигнал отсутствия аргумента и создавать новый объект внутри функции:def add_item(item, lst=None): if lst is None: lst = [] lst.append(item) return lstЭто обезопасит от накопления значений между вызовами. - Сравнение с None и булевыми значениями: Частая ошибка – использовать оператор равенства
==для проверки наNoneили булевы литералы. В Python корректнее делать такие проверки через операторis. Например,if x is None:вместоif x == None:. Это не только стиль (PEP 8 однозначно рекомендуетis None), но и защита: перегруженный метод__eq__объекта мог бы дать неожиданный результат при сравнении с None. Аналогично, не пишитеif is_valid == True:– простоif is_valid:проверит истинность значения. Сравнение сTrue/Falseизбыточно и может быть ошибочным (например,is_valid == Trueне поймает ситуацию, когдаis_validравно 1 или"True"). PEP 8 гласит: “Don’t compare boolean values to True or False using==”. И уж точно не стоит сравнивать черезis True/False– такие проверки работают не так, как многие ожидают. - Использование
isдля строк и чисел: Обратная ситуация – злоупотребление операторомisтам, где нужно сравнение равенства. Операторisпроверяет идентичность объектов (т.е. находятся ли они по одному адресу). В случае малых чисел и коротких строк Python может интернированно кешировать объекты, иa is bслучайно даст True, хотя смысл сравнения – сравнить значение. Поэтому для сравнения содержимого строк, чисел и других значений используйте==, аisтолько для None или одиночных объектов (синглтонов) вродеTrue/Falseили при намеренном сравнении идентичности. - Смешивание табуляции и пробелов: Ошибка форматирования может привести к
IndentationErrorили, хуже, к логическим ошибкам без явного исключения. Убедитесь, что в проекте единообразные отступы – только 4 пробела на уровень, никаких символов табуляции. Современные редакторы умеют отображать символы табуляции, либо сразу вставлять пробелы при нажатии Tab. Настройте окружение так, чтобы не вносить случайно табы. Если редактируете код, доставшийся от других, и в нём использованы табы, лучше сразу прогнать автоформатер (например,autopep8илиblack) для приведения к общему стилю. - Ошибки области видимости (scope): Новички иногда ожидают, что переменная, измененная внутри функции, изменит и внешнюю переменную. Например:
count = 5 def increment(): count += 1 # UnboundLocalError! increment()Это вызоветUnboundLocalError, потому что присваивание внутри функции создаёт локальную переменнуюcount, и интерпретатор не считает её связанной с внешней. Чтобы изменить глобальную переменную внутри функции, нужно объявить ееglobal count(что, однако, не является хорошей практикой). Совет: избегайте изменения глобальных переменных внутри функций – лучше возвращайте результат и присваивайте его снаружи, или инкапсулируйте состояние в объект. А если всё же нужно – не забудьтеglobal. Аналогичная ситуация с замыканиями: для изменения переменной из внешней области видимости внутри вложенной функции используйтеnonlocal. - Блок
try/except“ловит всё”: Конструкцияexcept:без указания конкретного исключения поймает абсолютно все исключения, включая системные (KeyboardInterrupt,SystemExit), что обычно нежелательно. Такой код может затруднить остановку программы по Ctrl+C или скрыть реальную ошибку. Правильнее перехватывать конкретные ожидаемые исключения. Если нужно перехватить вообще всё, используйтеexcept Exception:, чтобы не ловить системные сигналы (базовый класс Exception не включает их). Еще одна рекомендация – держать минимум кода внутри блока try, чтобы не перехватывать исключения из нецелевых мест. - Игнорирование менеджеров контекста: Иногда пишут так:
f = open('data.txt') data = f.read() # ... обработка ... f.close()Если при обработке случится исключение,close()может не выполниться, файл останется открытым. Правильнее:with open('data.txt') as f: data = f.read()Блокwithгарантирует закрытие файла по выходу, даже при ошибке. Это касается и других ресурсов: сетевых соединений, блокировок, транзакций БД (для них тоже могут быть контекстные менеджеры). Всегда используйтеwithтам, где он предусмотрен. - Wildcard-импорт (
from module import *): Такой импорт подтягивает в пространство имён все глобальные объекты модуля. Это плохая практика в реальном проекте, так как создает неявные зависимости и может привести к конфликту имён (перекрытию функций/переменных). PEP 8 прямо утверждает: “Wildcard imports should be avoided, as they make it unclear which names are present in the namespace” – импорт через*затрудняет понимание кода и работу инструментов. Используйте явный импорт:from module import func1, func2или простоimport moduleи обращайтесь какmodule.func1. Wildcard-импорт допустим разве что в интерактивной консоли для экономии времени или в некоторых редких случаях внутри пакета (когда в__init__.pyнужно реэкспортировать API из подмодулей). - Опечатки и регистр символов: Python – регистрозависимый язык. Переменные
dataиData– разные. Частая мелкая ошибка – опечататься в имени (например, использовать переменнуюmy_list, а потом обратиться кmylist). Хороший редактор или IDE подсветят такое несоответствие. Стоит настроить инструменты типа pylint/flake8 – они предупреждают о неиспользуемых переменных, возможных опечатках и т.д. Например, если вы написали переменную и нигде не прочитали – линтер сообщит, что она не используется, возможно это орфографическая ошибка где-то. - Нарушение соглашений стиля (PEP 8): Это нельзя назвать ошибкой в смысле бага, но несоблюдение стиля (именование с неправильным регистром, неконсистентные отступы, отсутствие пробелов вокруг операторов) – частый недостаток кода, снижающий его качество. Решение – освоить и применять на практике PEP 8 (см. раздел 2) или, как минимум, пропускать код через инструменты форматирования. Многие ошибки стиля легко устраняются автоматически (например, автоформатер
blackрасставит отступы и пробелы по стандарту, исчерпывая целый класс “ошибок”).
Как избегать ошибок: Лучший способ – учиться на чужом опыте и использовать инструменты. Регулярно читайте про распространённые ошибки в Python (статьи, чеклисты). Например, документация “Common Gotchas” в Hitchhiker’s Guide to Python описывает ряд таких подводных камней. Включите линтеры в процесс разработки: flake8, pylint, mypy – они поймают многое из вышеперечисленного до того, как код попадет в репозиторий. Пишите тесты, которые выявят логические ошибки. И, конечно, делайте code review: свежий взгляд коллеги может заметить оплошности, которые вы упустили.
В итоге, внимательность и дисциплина – залог того, что вы не попадётесь на типичные ловушки. А даже если попались – постарайтесь понять причину и запомнить. Со временем выработается чутьё, и подобные ошибки станут редкостью.
10. Примеры хорошего и плохого кода
Чтобы закрепить вышесказанные рекомендации, рассмотрим несколько пар примеров – неудачного (плохого) кода и его улучшенной, хорошей версии. Эти примеры демонстрируют применение лучших практик на практике.
Пример 1: Именование, форматирование и документация
Плохо:
# Пример неудачного стиля и имен
import math, sys
DATA = [1,2,3,4,5]
def ProcessData(d):
"""Process data list"""
sum=0
for x in d: sum+=x
return sum
class sampleclass():
def __init__(self, Value):
self.Value=Value
Недостатки этого фрагмента:
- Нарушены соглашения по стилю: имя функции
ProcessDataс заглавной буквы (неsnake_case), имя классаsampleclassс маленькой буквы (нарушен CapWords), имя параметраValueс большой буквы (PEP 8 советует использовать lower_case для аргументов). - Есть импорт нескольких модулей в одной строке (
import math, sys), что противоречит PEP 8 (импорты следует разделять построчно для ясности). - Отсутствуют пробелы после запятых в списке
DATA = [1,2,3,4,5]– тяжело читать. - В теле функции
ProcessDataвсё содержимое написано в одну строку, нет отступа на новой строке послеfor(нарушение правил форматирования, резко снижает читабельность). - Имя переменной
sumзатеняет встроенную функциюsum– плохая практика именования. - Docstring функции
ProcessDataбесполезен: “Process data list” не дает никакой новой информации, он просто перефразирует имя. Не описано поведение (что именно происходит с данными). - Класс
sampleclassне следует стилю имён, и его атрибутValueзаписан с большой буквы, плюс присваивание атрибуту без пробелов вокруг=.
Хорошо:
import math
import sys
DATA = [1, 2, 3, 4, 5]
def process_data(data: list[int]) -> int:
"""Вычислить сумму элементов списка data."""
total = 0
for x in data:
total += x
return total
class SampleClass:
"""Пример класса с корректным стилем именования."""
def __init__(self, value: float) -> None:
self.value = value
Что исправлено и улучшено:
- Имена приведены к соглашениям PEP 8: функция
process_dataв нижнем регистре с подчёркиванием, классSampleClassс заглавной. Параметрvalue– в нижнем регистре. Глобальная константаDATA– заглавными буквами. - Импорты разделены на две строки, как рекомендует PEP 8 (каждый модуль отдельно).
- В списке
DATAдобавлены пробелы после запятых, что соответствует стилю и улучшает видимость отдельных элементов. - В функции
process_dataкаждая операция на своей строке с 4-пробельным отступом. Цикл и суммирование оформлены согласно стандартному стилю. Переменнаяtotalпонятнее, чемsum(и не затеняет built-insum). - Добавлена содержательная docstring: она кратко описывает, что делает функция. Не дублируется имя, а по сути объясняется результат.
- В класс
SampleClassдобавлена docstring, пусть даже простая. Имена атрибута –self.valueвместоself.Value. Пробелы вокруг=при присвоении. Добавлены аннотации типов для конструктора (например,value: float), что улучшает информативность. - Обратите внимание, аннотации
-> intи-> Noneдля функцийprocess_dataи__init__соответственно – теперь сразу видно, что функция возвращает число, а конструктор не возвращает ничего (кроме неявного None).
В итоге, улучшенный код соответствует лучшим практикам: его стиль ровный, имена понятные, и он снабжен минимальной необходимой документацией.
Пример 2: Аннотации типов и обработка ошибок
Плохо:
def connect(db_config):
# Подключение к базе, возвращает объект соединения или None
import psycopg2
try:
conn = psycopg2.connect(db_config['dsn'])
return conn
except:
# любое исключение просто глушится
return None
conn = connect({"dsn": "dbname=test"})
if conn == None or conn == False:
print("Connection failed")
else:
print("Connected!")
Недостатки:
- Функция
connectне имеет аннотаций, неясно, что заdb_config(словарь? строка? объект конфигурации?). - Импорт внутри функции (
import psycopg2) – это допустимо, но обычно выносят импорты наверх модуля. В данном случае, возможно, сделано намеренно, но отмечаем. - Блок
except:перехватывает любое исключение и превращает его в None, не логируя ошибку. Это затрудняет отладку (соединение может не установиться по разным причинам, а мы их не узнаем). - В коде проверки соединения используется сравнение
if conn == None or conn == False. Тут сразу несколько проблем:- Для проверки None надо использовать
is None, а не== None. - Сравнение с False вообще странное – объект соединения никогда не равен False (если
psycopg2.connectпровалится, мы вернем None, а False не вернем). Возможно, автор хотел учесть, что функция может вернуть False, но это не документировано. - Такая логика нечитаема – лучше явно проверить на None.
- Для проверки None надо использовать
- При удачном подключении печатается “Connected!”, но в случае неудачи мы теряем инфу о причине (см. выше).
Хорошо:
import psycopg2
from psycopg2 import OperationalError, DatabaseError
def connect(db_config: dict) -> psycopg2.extensions.connection | None:
"""Установить соединение с БД PostgreSQL.
Args:
db_config (dict): Конфигурация БД, должна содержать ключ 'dsn'.
Returns:
Объект соединения или None, если подключение не удалось.
"""
dsn = db_config.get('dsn')
if dsn is None:
raise ValueError("DSN string is required in db_config")
try:
conn = psycopg2.connect(dsn)
return conn
except (OperationalError, DatabaseError) as e:
# Логируем ошибку и возвращаем None
print(f"Database connection error: {e}")
return None
conn = connect({"dsn": "dbname=test"})
if conn is None:
print("Connection failed")
else:
print("Connected!")
Что сделано:
- Импорты вынесены вне функции, и перечислены конкретные исключения, которые мы собираемся обрабатывать (
OperationalError,DatabaseError). Это улучшает читаемость и слегка ускоряет (импорт не выполняется каждый раз при вызове функции). - Добавлены аннотации: параметр
db_configожидается словарём, возвращаемый тип – объект соединения или None (используем синтаксис| None, доступный с Python 3.10, что эквивалентноOptional[connection]). - Docstring функции поясняет требование (наличие
'dsn'в конфиге) и поведение (возвращает None при неудаче). - Перед попыткой соединения явно проверяется наличие
dsnв конфиге. Если отсутствует – бросается исключениеValueErrorс понятным сообщением. Это лучше, чем получить KeyError где-то внутри psycopg2. - Блок
exceptловит только ожидаемые исключения подключения к базе. Мы перехватываем их, логируем сообщение об ошибке (через print, но в реальном коде лучше использовать logging). Важно: мы не перехватываем все исключения – если вдруг вылетит что-то непредвиденное (например,NameErrorвнутри функции или KeyboardInterrupt), оно не будет заглушено. - Логика проверки результата соединения вне функции упрощена до
if conn is None:– понятно, что если None, то подключение не удалось. Условиеconn is Noneгораздо яснее, чем предыдущий вариант, и корректнее с точки зрения стиля. - В сообщении об ошибке мы хотя бы выводим текст исключения
e. В боевом коде, возможно, стоило бы выбросить исключение дальше или записать в лог, но для примера достаточно печати.
Итог: улучшенная версия не подавляет все ошибки молча, а обрабатывает ожидаемые ситуации. Код снабжён аннотациями и документирован. Проверка результата соединения приведена в соответствие с рекомендациями (используется is None). Такой код и работать будет надёжнее, и поддерживать его значительно проще.
Пример 3: Ошибка с изменяемым default-значением
Плохо:
def add_to_collection(item, collection=[]):
collection.append(item)
return collection
print(add_to_collection(1)) # [1]
print(add_to_collection(2)) # [1, 2] - неожиданный результат?
Как обсуждалось ранее, этот код использует список [] как значение по умолчанию для параметра collection. В результате при втором вызове в коллекции уже лежит предыдущий элемент. Для функции, которая по смыслу должна каждый раз возвращать новый список с одним элементом (если не передан существующий), это ошибка. Фактически, collection здесь выступает как статическая переменная, сохраняя состояние между вызовами.
Хорошо:
def add_to_collection(item, collection=None):
"""Добавить item в коллекцию (список). Если коллекция не передана, будет создан новый список."""
if collection is None:
collection = []
collection.append(item)
return collection
print(add_to_collection(1)) # [1]
print(add_to_collection(2)) # [2] - теперь каждый вызов независим
Исправлено с применением шаблона None-sentinel:
- Если
collectionне передана, по умолчанию она равна None. - Внутри функции проверяем
if collection is None: collection = [], тем самым создавая новый список для каждого вызова. - Теперь функция работает ожидаемо: каждый вызов без аргумента будет получать свой новый список.
- Добавлена docstring, поясняющая поведение (особенно важно указать, что происходит, если коллекция не задана).
- Такой код безопасен и соответствует рекомендациям (мы явно сравниваем с None, а не с
== None).
Пример 4: Хороший vs плохой Python-код (суммарный пример)
Напоследок приведём сравнение двух фрагментов, иллюстрирующих множество аспектов сразу.
Вариант 1 (плохой):
import os, sys
def DoSomething(FileList):
# Выполняет некую операцию над списком файлов
res=[];
for i in range(len(FileList)):
file=FileList[i]
f = open(file); data = f.read(); f.close()
if data!=None and data!="":
res.append(len(data))
return res
print(DoSomething(["file1.txt","file2.txt"]))
Основные недостатки:
- Импорты в одну строку (os, sys) – лучше раздельно, но здесь sys даже не используется.
- Функция
DoSomethingназвана с заглавной буквы и глагол+сущность слитно – не в стиле Python. Лучшеdo_somethingили, лучше, дать осмысленное название, отражающее суть операции. - Параметр
FileListс заглавной буквы – не соответствует стилю, и по смыслу это iterable, лучше назватьfiles. - Документация: есть комментарий, но он дублирует имя. Неясно, например, что функция возвращает.
- Реализация:
for i in range(len(FileList))– не питонисто. Для перебора элементов списка в Python есть прямой способ:for file in FileList:. - Переменная
fileзатеняет встроенное имяfile(тип). Лучше избегать таких совпадений. - Чтение файла сделано вручную с открытием и закрытием – явное закрытие можно заменить на
with. - Проверка
if data != None and data != ""– избыточно. Если файл пустой,dataбудет пустой строкой, которая в логическом контексте False, поэтому достаточноif data:. А на Nonedataне может быть, потому чтоread()вернёт строку (пусть даже пустую). Так что проверка на None лишена смысла. К тому же, сравнение с None – опять через!=, а неis not. - Добавление в список результатов: ок, но
res=[]лучше отделять пробелами,res = []. - Нет аннотаций типов, непонятно что принимает/возвращает функция без чтения кода.
- После выполнения функции результат просто печатается. В реальном коде, наверное, что-то дальше бы делалось.
Вариант 2 (хороший):
import os
def count_file_lengths(files: list[str]) -> list[int]:
"""Вернуть список длин содержимого для каждого файла из списка files."""
lengths: list[int] = []
for filename in files:
# Читаем файл, если он не пустой
try:
with open(filename, 'r') as f:
data = f.read()
except FileNotFoundError:
print(f"Warning: file {filename} not found.")
lengths.append(0)
continue
if data:
lengths.append(len(data))
else:
lengths.append(0)
return lengths
result = count_file_lengths(["file1.txt", "file2.txt"])
print(result)
Улучшения:
- Импорт
osоставлен,sysубран как неиспользуемый. (Предположим, os тут нужен для чего-то вне фрагмента – если нет, тоже бы убрали). - Функция переименована в
count_file_lengths– теперь по имени понятно, что она делает (считает длины файлов). Имя параметраfiles– во множественном числе, отражает список. - Добавлена аннотация: функция принимает список строк (путей файлов) и возвращает список целых. Это сразу снимает часть вопросов.
- Docstring описывает поведение (что возвращается). Кратко, но информативно.
- Перебор файлов через прямой цикл
for filename in files:– понятнее и идиоматичнее, чем индексы. - Используется менеджер контекста
with openдля чтения файла – гарантированно закрытие. - Добавлен обработчик исключения
FileNotFoundError– на случай, если файл из списка не найден, мы не хотим прерывать всю функцию. Мы печатаем предупреждение и кладём 0 в результат для этого файла, затем продолжаем цикл (continue). - Проверка содержимого файла:
if data:– пустая строка оценивается как False, None тут не появится (если файл существует, data всегда строка). Поэтому это верный питоничный способ проверки “непустости”. - Если файл пустой, в lengths добавляем 0, если не пустой – длину содержимого.
- Итого, функция всегда возвращает список чисел той же длины, что и список файлов (для несуществующих или пустых файлов ставим 0). Это оговорено в доке.
- В конце пример использования: вызываем функцию и печатаем результат. Функция не печатает внутри себя ничего (кроме предупреждений), а возвращает чистый результат, что правильно для функций. Мы отделили “логику” и “интерактив” печати.
Этот пример демонстрирует сразу несколько аспектов: улучшенное именование, обработку ошибок, использование with, логическую проверку на пустые данные, аннотации и документацию. Код стал длиннее на пару строк, но при этом явно надёжнее и понятнее. Такой код легче модифицировать (например, вместо print можно изменить логику обработки отсутствующих файлов без риска сломать всё остальное).
Подводя итог, принципы чистого кода на Python в 2026 году сводятся к: придерживайтесь согласованного стиля (PEP 8), пишите понятные имена, документируйте поведение, покрывайте код тестами, используйте возможности языка (аннотации, менеджеры контекста, генераторы) и учитесь на типичных ошибках. Это делает ваш код не только эстетически приятным, но и надёжным в долгосрочной перспективе. Как сказал Тим Питерс в своем «Дзен Python», “If the implementation is easy to explain, it may be a good idea.” – если реализацию легко объяснить, возможно, это хорошая идея. Следуя лучшим практикам, вы как раз и стремитесь писать такой код – простой, понятный и идиоматичный. Это окупается сторицей на этапах сопровождения и развития проектов.
Источники и ссылки:
- PEP 8 – Style Guide for Python Code – официальный гид по стилю Python.
- PEP 20 – The Zen of Python – философия Python, 20 афоризмов о дизайне кода.
- PEP 257 – Docstring Conventions – соглашения по оформлению строк документации.
- Hitchhiker’s Guide to Python – раздел Common Gotchas (неявные “подводные камни” Python).
- Статья “Python Testing Best Practices: 2025” – о современных подходах к тестированию, Pytest.
- Опрос “Python Typing Survey 2025” – результаты по применению типизации (86% используют часто).
- Real Python – “Python Best Practices – Project Layout” – рекомендации по структуре проекта.
- Обсуждение на Python.org: “Wildcard imports” – почему не рекомендуется
import *



