"Give me six hours to chop down a tree and I will spend the first four sharpening the axe."
Open-source проекты, сторонние инструменты и библиотеки - это то, за что мы действительно любим Python. В этой статье я собрал самые полезные, валидированные сообществом и проверенные временем инструменты, конфигурации которых можно встретить в популярных проектах с открытым исходным кодом.
Инструменты распределены по этапам/сферам разработки. По каждому из них я дам небольшое описание и попытаюсь рассказать о его пользе. Если утилита имеет дополнительные расширения/плагины, то я расскажу про самые полезные (на мой взгляд).
Личный проект
Как пример интеграции инструментов из этой статьи, можно открыть репозиторий с исходным кодом моего проекта. HackerNews Alerts Bot (GitHub) - Telegram-бот для получения различных уведомлений с форума Hacker News. Помимо просмотра конфигураций, чтобы быть в курсе актуальных новостей и статей, можно запустить этот бот и подписаться на уведомления о новых постах по ключевому слову. Например, чтобы подписаться на посты о Python, введите: /add python -stories
.
Список инструментов
-
Конвейер интеграции
1.1 pre-commit
-
Управление зависимостями
2.1 pip-compile
-
Качество кода
3.1 flake8
3.2 Black
3.3 isort
3.4 Mypy
3.5 Ruff: замена flake8 и isort
-
Тестирование
4.1 pytest
-
Дебаггинг
5.1 PySnooper
-
Терминал
6.1 IPython
6.2 Rich
1. Конвейер интеграции
Чтобы успешно интегрировать инструменты из этой статьи в Python проект нам необходима надежная система, которая возьмет на себя работу по их установке, обновлению и запуске при наступлении определенного события. State of the art инструментом в этой категории безусловно является pre-commit.
1.1 pre-commit
pre-commit — это фреймворк, использующий git pre-commit hook для запуска хуков (инструментов) перед созданием коммита. Помимо запуска инструментов, pre-commit также берет на себя их установку и обновление.
pre-commit пользуется большой популярностью среди open-source Python проектов, например .pre-commit-config.yaml
может быть найден в репозиториях наиболее популярных веб-фреймворков: Django, Flask и FastAPI. Это тот самый инструмент, конфиг которого я добавляю первым в новый проект.
Гайд по установке, Quick start и другая полезная информация доступна в документации.
В следующем разделе я расскажу о расширениях, которые обязательно пригодятся в вашей конфигурации. Хуки инструментов из этой статьи указаны в разделе «pre-commit хук» соответствующей утилиты.
Полезные хуки
Стандартная конфигурация уже имеет некоторые расширения из pre-commit-hooks репозитория. Кроме того, репозиторий содержит достаточное количество готовых хуков, которые можно легко добавить в .pre-commit-config.yaml
.
pyupgrade
pyupgrade — это хук от создателя pre-commit для автоматического обновления синтаксиса языка. Например, pyupgrade перепишет простые вызовы str.format()
в f-strings.
Добавить расширение в конфиг pre-commit:
- repo: <https://github.com/asottile/pyupgrade>
rev: v3.3.1
hooks:
- id: pyupgrade
typos
typos — spell-checker, написанный на rust, для проверки исходного кода. Хук отличается высокой скоростью проверки и минимальным количеством ложных срабатываний.
- repo: <https://github.com/crate-ci/typos>
rev: v1.13.4
hooks:
- id: typos
2. Управление зависимостями
С управлением зависимостей в Python не все однозначно. Poetry, pipenv, conda - все эти библиотеки являются популярным выбором среди пользователей языка. Моя причина выбора pip-compile довольно-таки проста: pip-compile позволяет мне пользоваться стандартными инструментами (venv и pip), решая основные проблемы управления зависимостями. Ни больше ни меньше.
2.1 pip-compile
Зачем нужен pip-compile?
Перечислю несколько проблем, возникающих при заморозке зависимостей с помощью pip freeze
:
Нельзя определить, откуда взялась какая-либо из зависимостей, так как
pip freeze
сохраняет все вперемешку (Пакеты, от которых наш проект зависит напрямую, и зависимости этих пакетов).Добавляя новый пакет в
requirements.txt
, мы не фиксируем его собственные зависимости. Новые версии этих незакрепленных библиотек могут привести к трате времени на дебаггинг таких обновлений.Редактирование версии пакета в
requirements.txt
оставляет без внимания зависимости этой библиотеки. Новые пакеты не добавляются, а старые - не удаляются, что снова приводит к проблемам на этапе сборки.
Для решения этих проблем нам нужно хранить два файла с зависимостями. Первый файл (requirements.in
) будет содержать пакеты, которые мы будем добавлять вручную. Во втором файле (requirements.txt
) будут храниться закрепленные версии каждой библиотеки нашего проекта, включая пакеты, добавленные вручную (и их зависимости). Список библиотек requirements.in
нужен для генерации второго файла, того самого requirements.txt
, который мы используем при сборке проекта.
pip-compile - это инструмент, с помощью которого можно осуществить такой подход управления зависимостями.
Управление зависимостями с помощью pip-compile
pip-compile - это одна из утилит пакета pip-tools, поэтому, чтобы начать им пользоваться, установим эту библиотеку (PyPI).
После установки пакета создайте файл requirements.in
. Сюда мы будем добавлять зависимости напрямую, например:
Flask
Теперь можно вызвать pip-compile
. Эта команда генерирует итоговый requirements.txt
файл:
$ pip-compile
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile
#
click==8.1.3
# via flask
flask==2.2.2
# via -r requirements.in
itsdangerous==2.1.2
# via flask
jinja2==3.1.2
# via flask
markupsafe==2.1.1
# via
# jinja2
# werkzeug
werkzeug==2.2.2
# via flask
Отклик команды выводит сгенерированное содержимое requirements.txt
. Комментарии в начале файла напоминают нам, как сгенерировать файл. Каждая зависимость зафиксирована. По самой структуре файла мы можем понять, почему был установлен тот или иной пакет.
pip-compile решает множество проблем, не привнося серьёзной абстракции в ваш проект. Все, что нужно для базового использования - это всего лишь запомнить одну команду.
Про конвертирование существующего requirements.txt
, описание процесса добавления/удаления зависимостей и обновление пакетов рассказано в документации pip-tools. Также в pip-tools присутствует команда pip-sync - синхронизация виртуального окружения с requirements.txt
. Используйте ее с особой осторожностью, pip-sync
удаляет все зависимости неупомянутые в requirements.txt
.
pre-commit хук
- repo: <https://github.com/jazzband/pip-tools>
rev: 6.12.0
hooks:
- id: pip-compile
3. Качество кода
Под инструментами качества кода подразумеваются линтеры, средства форматирования и проверки кода. Здесь указан полный набор инструментов, которые дополнят друг друга при минимальной настройке конфигураций. Ну и конечно же, все они поместятся в наш кейс (pre-commit).
3.1 flake8
flake8 — это инструмент, сканирующий код проекта на наличие ошибок и соответствие заданным стандартам. По сути, это полноценный линтер, использующий под капотом три других утилиты (pycodestyle, pyflakes и mccabe), который также может быть расширен с помощью пользовательских плагинов.
flake8 используется в Django, Flask и многих других open-source проектах. За время существования (flake8 появился в 2010) было создано множество различных плагинов. Про некоторые из них я расскажу в разделе «Полезные плагины».
Про настройку конфига и игнорирование ошибок можно почитать в официальной документации.
pre-commit хук
Хук flake8 должен быть указан после средств форматирования кода (Black, isort, и т. д.). В противном случае линтер будет ругаться на ошибки, которые будут автоматически исправлены этими инструментами.
- repo: <https://github.com/pycqa/flake8>
rev: 6.0.0
hooks:
- id: flake8
Полезные плагины
flake8 сам по себе неплохой линтер, но его полный потенциал можно раскрыть при использовании сторонних плагинов.
flake8-bugbear
flake8-bugbear — это плагин, содержащий дополнительные проверки на наличие ошибок и возможные проблемы с дизайном кода. Поддерживается разработчиками flake8.
Пример хука с добавленным плагином:
- repo: <https://github.com/pycqa/flake8>
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear
flake8-print
flake8-print — проверка на наличие вызовов print(). Поможет предотвратить коммиты со строками отладки.
- repo: <https://github.com/pycqa/flake8>
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear
- flake8-print
Полноценный список валидированных расширений можно найти в awesome-flake8-extensions репозитории.
3.2 Black
Black — это инструмент форматирующий код на основе стандарта PEP 8.
Несмотря на то, что проект вышел из беты только в этом году, Black уже используется многими известными компаниями и популярными open-source проектами: Django, Facebook, Mozilla, pandas и т. д. Кроме того, проект официально поддерживается Python Software Foundation, организацией, занимающейся разработкой Python.
Контроль над конфигурацией инструмента у вас будет минимальный. Это философия проекта. Но все же некоторые настройки можно подстроить под ваши нужды. Всю необходимую информацию для установки, интеграции и настройки Black можно найти в документации.
pre-commit хук
В документации есть поле language_version, но в нем нет необходимости, если вы указали версию Python через опцию конфига default_language_version.
- repo: <https://github.com/psf/black>
rev: 22.12.0
hooks:
- id: black
3.3 isort
Black хорош как инструмент приведения кода к общему стилю. Для организации строк импорта нам понадобится другая утилита.
isort — это средство форматирования кода, предназначенное для сортировки и группировки строк импорта на основе различных критериев. Библиотека была выпущена в 2013 году. С тех пор isort стал основным инструментом сортировки импортов для большинства популярных open-source проектов.
Пример форматирования кода с помощью isort:
from __future__ import absolute_import
import os
import sys
from third_party import (lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8,
lib9, lib10, lib11, lib12, lib13, lib14, lib15)
from my_lib import Object, Object2, Object3
print("Hey")
print("yo")
Для интеграции инструмента вместе с black в конфигурации файла укажите профиль с одноименным названием:
[tool.isort]
profile = "black"
Установка и настройка утилиты описана в документации.
pre-commit хук
- repo: <https://github.com/pycqa/isort>
rev: 5.11.2
hooks:
- id: isort
3.4 Mypy
Тема статической проверки типов продолжает набирать обороты в Python. Самым популярным и развитым инструментом в этой категории является Mypy - open-source проект, разрабатываемый самим сообществом языка.
Основной стимул для использования Mypy - это нахождение багов, которые не подстать обычным линтерам. Инвестируя в аннотации и статическую типизацию, мы не только отлавливаем ошибки до их появления в runtime, но и в итоге получаем более надежный и читабельный код.
Так как наверняка ваш проект включает множество сторонних библиотек, то для эффективного использования Mypy необходимо скачать пакеты заглушек. Например, для проверки типов Django, мы можем установить заглушки django-stubs.
В отличие от большинства других инструментов, описанных в этой статье, Mypy и проверка типов - это обширная тема, требующая хотя бы базового изучения перед тем, как она сможет принести пользу. Документация Mypy включает руководство по началу работы, шпаргалку и справки по различным типам. Другую информацию по этой теме можно найти в awesome-python-typing репозитории.
pre-commit хук
- repo: <https://github.com/pre-commit/mirrors-mypy>
rev: v0.991
hooks:
- id: mypy
Более подробно об интеграции Mypy с pre-commit в репозитории расширения.
3.5 Ruff: замена flake8 и isort
Ruff — это новый, быстроразвивающийся линтер Python кода, призванный заменить flake8 и isort.
Основным преимуществом Ruff является его скорость. Ruff в 10-100 раз быстрее аналогов (Линтер написан на Rust). В сравнении с flake8, автор заявляет о практически полном совпадении с набором правил инструмента и нативной реализацией популярных плагинов (flake8-bugbear и т.д.). Также Ruff совместим с Black «из коробки».
Ruff может форматировать код. Например, он автоматически удаляет неиспользуемые импорты. Что касается сортировки и группировки строк импорта, то она практически идентична isort.
Несмотря на недавний выпуск инструмента (Август этого года), Ruff используется во многих популярных open-source проектах (FastAPI, Pydantic и т.д.).
Настройка инструмента осуществляется в pyproject.toml
файле. Установка и конфигурация описана в README.md проекта.
Не могу не упомянуть, что Ruff - это один из инструментов, о котором я узнал благодаря уведомлению по ключевому слову «Python» в моем Telegram-боте.
pre-commit хук
- repo: <https://github.com/charliermarsh/ruff-pre-commit>
rev: v0.0.189
hooks:
- id: ruff
4. Тестирование
Тестирование - это тот этап разработки, который сообщество точно не могло оставить в стороне. В этом разделе будет рассмотрен наиболее популярный инструмент для тестирования кода, возможности которого помогут эффективно писать качественные тесты.
4.1 pytest
pytest — это полноценный фреймворк, заменяющий unittest, стандартный модуль для написания unit-тестов.
Перечислю основные преимущества pytest:
pytest значительно упрощает процесс тестирования кода. Для написания простых тестов нам не нужно ничего импортировать, достаточно использовать обычные функции с приставкой test_. Кроме того, pytest заменяет все self.assert* методы модуля unittest одним утверждением assert.
Более информативный и читабельный вывод информации о ходе и результатах тестирования. Состояние системы, установленные плагины, детальный отчет при фейлах и т. д.
Фикстуры вместо
.setUp()
и.tearDown()
. Фикстуры в pytest - это функции, которые создают данные для набора тестов. Каждый тест может принимать фикстуры в качестве аргументов. Такой подход делает набор тестов более читаемым и структурированным.Возможность расширения фреймворка с помощью сторонних плагинов. За время существование проекта было создано более 800 плагинов.
По тестированию кода с помощью pytest написаны целые книги. Но для создания базовых тестов достаточно прочитать несколько статей из руководства по началу работы.
Effective Python Testing With Pytest
5. Дебаггинг
pdb является отличным инструментом для отладки Python программ. Однако его настройка может занять некоторое время или даже оказаться невозможной (Удаленные контейнеры). PySnooper - это золотая середина между вызовами print() и полноценным, интерактивным отладчиком.
5.1 PySnooper
PySnooper — это декоратор, с помощью которого можно получить полный лог функции, включая изменения локальных переменных, отработавшие строки и время их выполнения.
Установить PySnooper:
pip install pysnooper
Пример использования декоратора с рекурсивной функцией:
import pysnooper
@pysnooper.snoop()
def fact(x):
return x if x == 1 else x * fact(x - 1)
Вывод:
Source path:... .../example.py
Starting var:.. x = 4
23:55:58.173212 call 49 def fact(x):
23:55:58.173320 line 50 return x if x == 1 else x * fact(x - 1)
Starting var:.. x = 3
23:55:58.173367 call 49 def fact(x):
23:55:58.173420 line 50 return x if x == 1 else x * fact(x - 1)
Starting var:.. x = 2
23:55:58.173463 call 49 def fact(x):
23:55:58.173515 line 50 return x if x == 1 else x * fact(x - 1)
Starting var:.. x = 1
23:55:58.173556 call 49 def fact(x):
23:55:58.173610 line 50 return x if x == 1 else x * fact(x - 1)
23:55:58.173643 return 50 return x if x == 1 else x * fact(x - 1)
Return value:.. 1
Elapsed time: 00:00:00.000146
23:55:58.173727 return 50 return x if x == 1 else x * fact(x - 1)
Return value:.. 2
Elapsed time: 00:00:00.000324
23:55:58.173807 return 50 return x if x == 1 else x * fact(x - 1)
Return value:.. 6
Elapsed time: 00:00:00.000499
23:55:58.173886 return 50 return x if x == 1 else x * fact(x - 1)
Return value:.. 24
Elapsed time: 00:00:00.000740
Логирование части кода можно осуществить с помощью with
блока:
import pysnooper
import random
def foo():
lst = []
for i in range(10):
lst.append(random.randrange(1, 1000))
with pysnooper.snoop():
lower = min(lst)
upper = max(lst)
mid = (lower + upper) / 2
print(lower, mid, upper)
Сохранение логов в файл, логирование глобальных переменных и другую информацию по особенностям PySnooper можно найти в README.md и ADVANCED_USAGE.md файлах репозитория.
6. Терминал
Оболочка Python в том или ином виде - это незаменимый инструмент для быстрого выполнения небольших фрагментов кода и манипуляций с данными. Апгрейд этой среды непременно повысит нашу производительность. А ещe в терминале мы можем читать логи, просматривать traceback’и ошибок и получать результаты команд. Все это можно вывести в форматированном виде с помощью библиотеки Rich.
6.1 IPython
IPython — это усовершенствованная оболочка Python. Инструмент содержит такие функции как «магические» команды, подсветку синтаксиса и продвинутый автокомплит.
Для использования новой оболочки нам всего лишь нужно установить пакет IPython:
pip install ipython
В следующих разделах я расскажу о некоторых функциях оболочки. За вводным руководством и другой полезной информацией можно обратиться к обширной документации инструмента.
Сохранение истории вывода
Одной из полезных функций IPython является кеширование результатов вывода. Через нумерацию Out мы можем повторно использовать любой из предыдущих результатов:
In [3]: "пример"
Out[3]: 'пример'
In [4]: print(f" {_3} кеширования результатов вывода")
пример кеширования результатов вывода
In [5]: 10
Out[5]: 10
In [6]: _5 + 10
Out[6]: 20
Полезные команды
В этом разделе я указал пару команд, которые я использую постоянно. Список полезных команд не ограничивается этими двумя. Все доступные «магические» команды можно найти в разделе Built-in magic commands официальной документации.
Для замера времени выполнения кода существует команда %timeit
:
In [8]: %timeit [i for i in range(10000)]
144 µs ± 1.8 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
Для вывода введенной информации мы можем вызвать команду %hist
:
In [9]: %hist
"пример"
print(f" {_3} кеширования результатов вывода")
5
_5 + 10
%timeit [i for i in range(10000)]
%hist
6.2 Rich
Rich — это библиотека Python для вывода форматированного текста (цвет, стиль, подсветка синтаксиса, markdown) в терминал.
Rich поддерживает все платформы и терминалы. Это сверх популярная библиотека, которая используется для форматирование вывода в большинстве проектов (В том же pip, например).
Для работы с инструментом советую ознакомиться с документацией.
Полезные возможности
Progress display
Rich может отображать настраиваемый индикатор выполнения задач. По умолчанию будет отображаться описание задачи, процент выполнения, примерное оставшееся время и сам индикатор выполнения.
Rich Inspect
Кроме форматирования текста, библиотека содержит функцию для дебаггинга объектов Python. Пример использования:
from rich import inspect
from rich.color import Color
color = Color.parse("red")
inspect(color, methods=True)
Заключение
Практически каждый инструмент имеет свои «рабочие» альтернативы. Многие утилиты не были включены в этот список, так как я посчитал их слишком специфичными. Расскажите в комментариях, кто чем пользуется, возможно что-то упускаю и я!
На этом все. Надеюсь, статья была полезна, и вы узнали хотя бы об одном инструменте, который может быть задействован в ваших нынешних или будущих проектах.
Комментарии (7)
4uku
02.01.2023 22:37Спасибо за статью и ваш труд, как раз недавно начал заниматься пет-проектами, чтобы навык не терять, а теперь ещё и причешу их немного :)
vassabi
02.01.2023 23:13Black - сам использую, рекомендую
там только надо его поднастроить (например по умолчанию у него длина строки 80, что ИМХО коротковать нынче :) )
Andrey_Solomatin
05.01.2023 10:48Не так, чтобы люди сильно поменялись с тех пор. Я раньше тоже любил побольше, но теперь когда стал слидить за цикломатической и когнитивной сложностью кода, мне стало хватать.
masai
03.01.2023 12:18+8Навскидку добавлю.
pyenv — менеджер интерпретаторов и виртуальных окружений.
Hatch — относительно новая система сборки под крылом PyPA, но поддерживает все соответствующие PEP. Перешёл на неё с Poetry и не жалею.
Кстати, я заметил, что многие отказываются от систем сборки якобы из-за того, что придётся пользоваться новыми утилитами, а изучать их не хочется. Это не так. Можно перенести все зависимости в pyproject.toml (setup.py для простого перечисления зависимостей не нужен и не рекомендуется), указать систему сборки, отличную от setuptools и не пользоваться ничем кроме pip. Даже колёса можно собирать с помощью pip wheel (хотя лучше python3 -m build).
Из полезных особенностей, которых нет в setuptools — динамические версии из коробки (может сам читать версию из указанного файла), умеет создавать окружение для разработки, умеет запускать команды для матрицы версий (tox становится не нужен).
Hatch пока сам не умеет фиксировать зависимости (автор ждёт, когда такой функционал появится в pip), но можно пользоваться pip-tools. pip-compile отлично понимает зависимости в pyproject.toml.
pyright — тайпчекер. Мне он нравится больше mypy тем, что он быстры и просто работает из коробки без какой-либо настройки и телодвижений. Сам он написан не на Python (, но есть пакет, который можно поставить через pip. Он сам подтянет всё, что нужно. Из-за его скорости, его вполне можно добавить в pre-commit.
py-spy — профайлер. Киллер-фича — способность подключиться к уже запущенному процессу и показывать, какие именно функции сейчас работают. Как top/htop в линукс.
memray — профилировщик памяти.
Если ещё что-то вспомню, то напишу. :)
Пара дополнений про упомянутые в статье пакеты.
К сожалению, ruff пока трудно назвать заменой flake8, так как сила последнего в плагинах, но штука классная.
У rich классная визуализация исключений.
dyggery
03.01.2023 21:45+1Из полезных особенностей, которых нет в setuptools — динамические версии из коробки (может сам читать версию из указанного файла)
setuptools тоже умеет читать версию из файла
masai
04.01.2023 02:47Да, действительно. Я ошибся. Можно указать, откуда брать версию, в
tool.setuptools.dynamic
.Любопытно, что я видел это в документации, но забыл. Видимо, проекты, в которых в setup.py регуляркой достают версию из файла, создали стойкую ассоциацию, что setuptools самостоятельно это сделать не может. :)
Ryav
Что на счёт PDM?