Лучшие практики Python в 2021
Для начала
Цель данного Руководства – описать экосистему разработчика Python в 2021 году. Оно будет полезно любому, кто пришел в Python, зная другой язык программирования.
Они говорят, что ты должен вызубрить алгоритмы и структуры данных, что ты можешь выучить язык программирования за пару недель, ну, просто узнаешь синтаксис. Полностью согласен: алгоритмы и структуры данных – экстремально важны для разработчика, но когда говорим о языке программирования, то уточнить синтаксис – мало. Вокруг языка, как правило складывается, целая экосистема из инструментов, и “эталонных практик” – способов делать то-то и то-то наилучшим образом. Для кого-то пришедшего из другого языка, может быть утомительно изучать одновременно всю экосистему, особенно если учесть, что чаще всего ценнейшие данные по крупицам рассредоточены по разным местам.
Это моя, очень субъективная, попытка собрать в одном месте лучшие практики по настройке Python для локальной разработки. Также будут советы по интеграции этих тулзов с Visual Studio Code (однако не так уж обязательно работать именно в этом IDE). Гайд получился весьма большим, поскольку я подробно объясняю работу всех инструментов. В конечном итоге у тебя должно выйти – восхитительно настроенное окружение для Python.
Управление версиями Python в pyenv
Почему pyenv?
Видел десятки туториалов, начинающиеся с того же тупого совета: иди на python.org и скачай последнюю версию Python для своей платформы. Не делай так. Можно лучше, и вот почему.
Понятно, что версий Python – сильно больше одной, и тебе понадобится переключаться между ними, работая в разных проектах.
Возможно, есть версии Python, идущие в комплекте с операционкой. Например в Mac сейчас ставят по умолчанию версию 2.7, а некоторые дистрибутивы Windows поставляются с версией 3.х. Далее, в пакет Anaconda включена своя версия Python. Нужно учесть: никогда неизвестно, какая версия появится в ответе на введенную команду “python” в командной строке.
На каком-то этапе может возникнуть бардак с разными версиями ехе-файлов Python на твоем компьютере, и надо уметь управляться с ними. Если есть средство для этого, было бы отлично.
Да, такое есть. По ссылке –
Как ставится pyenv?
Установка на Mac:
brew update brew install pyenv
На Linux установка здесь:
curl https://pyenv.run | bash
На Windows – по ссылке.
Но все-таки лучше работать в Windows Subsystem for Linux (WSL), а затем установить “как правильно в Linux”.
Как работать с pyenv?
Сначала посмотрим на список всех ехе-шников Python, если они есть на компьютере. (По крайней мере, те которые pyenv может найти):
pyenv versions
* system (set by /Users/alex/.python-version)
Здесь показан вывод на моем компьютере. Звездочка-астериск обозначает текущую версию установленного Python. Если ввести команду:
python -V
то выведет сообщение, говорящее, что MacOS на моем компьютере все еще с Python 2.7.
Python 2.7.16
Теперь посмотрим, какие версии Python доступны:
pyenv install --list
Появится большой список, ты даже будешь удивлен, насколько большой. Имплементации CPython будут иметь версии типа 3.8.5, другие имплементации – что-то типа pypy 3.6-7.3.1.
Чтобы посмотреть только С-шные версии Питона, надо запустить следующую команду:
pyenv install --list | grep " 3\."
Если pyenv уже долго используется, и новых версий Питона как-то не выводится в pyenv, то вероятно, надо обновить сам pyenv:
brew upgrade pyenv
или так:
pyenv update
Как должно быть понятно, версии с приставкой -dev девелоперские.
Итак, я установил самый новый Питон и при этом самую стабильную версию (на момент написания поста):
pyenv install 3.8.5
Команда загружает исходник и компилирует его:
python-build: use openssl@1.1 from homebrew python-build: use readline from homebrew Downloading Python-3.8.5.tar.xz... -> https://www.python.org/ftp/python/3.8.5/Python-3.8.5.tar.xz Installing Python-3.8.5... python-build: use readline from homebrew python-build: use zlib from xcode sdk Installed Python-3.8.5 to /Users/alex/.pyenv/versions/3.8.5
Если запустить команду pyenv versions еще раз, то увидим нашу выбранную версию в списке, но она еще не активирована:
* system (set by /Users/alex/.python-version) 3.8.5
Надо верифицировать ее командой. Все еще нет:
Python 2.7.16
pyenv дает возможности установки любой версии Python как глобального Python-интерпретатора, но мы сейчас делать так не будем. У нас уже есть скрипты или другие инструменты, работающие на дефолтном интерпретаторе, поэтому давайте не менять эти настройки. Вместо этого, давайте настроим Python для каждого проекта отдельно. Итак, создадим проект в следующем разделе.
Есть и альтернативный инструмент для описанного выше – asdf, по ссылке.
Управление зависимостями в Poetry
Зачем мне нужен Poetry?
По умолчанию, пакеты Python ставятся командой pip install. В реальности никто так не делает. Эта команда приводит все зависимости к одному интерпретатору Python, что создает бардак.
Хорошей практикой является проставлять зависимости по-проектно. Таким образом проект имеет зависимости только нужные, и никаких других. Не возникают конфликты с другими питон-пакетами, позарез нужными в другим проектах.
Для этой проблемы придумано решение – виртуальные окружения. В них каждый проект имеет собственное виртуальное окружение с приписанной ему питон-версией, и фиксированными зависимостями, в каждом проекте своя.
Виртуальные окружения, которые возникли из старых venv, virtualenv, virtualenvwrapper – очень популярны и давно работают. Но есть некая неразбериха с pipenv, если очень надо, то можешь почитать здесь: Pipenv: promises a lot, delivers very little. Одной из видимых проблем pipenv является задержка релизов – например с 2018 года не было новых. Но в середине 2020 года проект pipenv внезапно “проснулся” и выпустил пару апдейтов. Poetry тоже применяется, к тому же заявляют что он лучше работает чем pipenv. Poetry может быть полезен в опенсорсных проектах, особенно в публикации пакетов.
Официальный сайт Poetry
Как ставится Poetry
Рекомендую ставить Poetry на системном уровне.
В MacOS, Linux и WSL вот так:
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
Если работаешь в Windows Powershell, есть специальный скрипт на официальном сайте. Но видимо лучше просто сделать в WSL.
Чтобы применить изменения для текущей версии оболочки, команда:
source $HOME/.poetry/env
Можно добавить это в авторан-скрипт типа .bashrc или .zshrc, если Poetry все еще нет в новой сессии:
export PATH="$HOME/.poetry/bin:$PATH"
Можно тут же включить Tab-автодополнение в оболочке. Описано тут.
Как создать проект в Poetry?
Poetry создает отдельные папки и переходит туда, поэтому надо убедиться, что выполнен переход из текущей папки в папку Python-проекта, и там запустить команду:
poetry new my-project
где my-project это имя твоего проекта.
Дальше посмотрим, что там создал Poetry:
cd my-project; ls
Как настроить версию Python по-проектно
Мы все еще находимся на версии 2.7, помнишь? В новом проекте мы будем работать в новой версии, поэтому перейдем в pyenv. Мы сейчас находимся в проектной папке, и мы задаем версию Python локально, для этой папки, командой:
pyenv local 3.8.5
Если мы запустим pyenv versions сейчас, то увидим что 3.8.5 обозначено звездочкой, как активная для этой папки. Учти, что вне этой папки версия Python остается не измененной, той же “по умолчанию”.
system * 3.8.5 (set by /Users/alex/iCloud/dev/projects/my-project/.python-version)
Можно проверить наверняка, командой python -V.
Python 3.8.5
Теперь заставим Poetry “подхватить” текущую версию:
poetry env use python
Creating virtualenv my-project-PSaGJAu6-py3.8 in /Users/alex/Library/Caches/pypoetry/virtualenvs Using virtualenv: /Users/alex/Library/Caches/pypoetry/virtualenvs/my-project-PSaGJAu6-py3.8
На последнем шаге настройки версий, давай-ка обновим pyproject.toml
. [tool.poetry.dependencies]
. Должно ответить, что поддерживается версия 3.8 или выше:
[tool.poetry] name = "my-project" version = "0.1.0" description = "" authors = ["Alex"] [tool.poetry.dependencies] python = "^3.8" [tool.poetry.dev-dependencies] pytest = "^5.2" [build-system] requires = ["poetry-core>=1.0.0a5"] build-backend = "poetry.core.masonry.api"
Как работать в Poetry
На этом этапе надо будет установить первую зависимость: какую-то библиотеку или фреймворк, “поверх” того что планировалось в проекте. Например:
poetry add aiohttp
где aiohttp
– фреймворк который ставится.
Если надо подтянуть существующий проект и установить его зависимости для локальной разработки, вводим команду:
poetry install
Обновить все зависимости к последним версиям:
poetry update
Как связать Poetry с VS Code
VS Code к сожалению не подхватывает Poetry автоматически. Но есть и хорошие новости, можно заставить VS Code находить и подхватывать виртуальное окружение, прописанное в Poetry.
Скачала активируем виртуальное окружение:
poetry shell
Spawning shell within /Users/alex/Library/Caches/pypoetry/virtualenvs/my-project-PSaGJAu6-py3.8 ➜ my-project . /Users/alex/Library/Caches/pypoetry/virtualenvs/my-project-PSaGJAu6-py3.8/bin/activate (my-project-PSaGJAu6-py3.8) ➜ my-project
Теперь мы вошли в виртуальное окружение, и можем вызывать VS Code из него:
code .
Надо будет также установить Pylance – питон-сервер для VS Code.
При открытии любого питон-файла, VS Code тут же спросит, какой интерпретатор задействовать?
Итак, выберем. Мы вызывали VS Code из виртуальной среды, поэтому нужный интерпретатор покажется в списке, и мы выберем его.
Заметь, что есть две опции для 3.8.5, мы должны выбрать одну, которая относится к виртуальному окружению (это видно по пути к файлу, там должно быть virtualenv).
Вообще, можно подписаться на соответствующую issue на Гитхабе, по связке VS Code с Python. А Брет Кэннон говорит, что разработчики VS Code уже переписывают код связки, и когда-нибудь, говорили они, Poetry будет полностью поддерживаться в VS Code.
Обновление Poetry
Просто запускай poetry self update, и получишь последнюю версию Poetry.
Если выплыла ошибка:
ImportError: No module named cleo
То надо переустановить Poetry. Сначала удалить:
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py > get-poetry.py python get-poetry.py --uninstall python get-poetry.py rm get-poetry.py
Как обновить версию Python в Poetry и pyenv?
Новые версии Python выходят регулярно, там новые функции и фиксы, и мы хотим обновить Python у себя. Это сделать достаточно просто.
Сначала мы загружаем, компилируем, и настраиваем новый Python-интерпретатор. Убедись, что запускаешь эти команды не из виртуального окружения, а откуда положено – из корневой папки проекта. В моем случае, надо было обновиться к CPython 3.9.2 (которая была новейшей на момент написания основной части этого текста), но в принципе ничего особо не отличается в любой другой версии CPython.
pyenv install 3.9.2 pyenv local 3.9.2 pyenv versions
my-project pyenv versions system 3.8.5 * 3.9.2 (set by /Users/alex/iCloud/dev/projects/my-project/.python-version)
Дальше мы в Poetry передаем этот интерпретатор как “рабочий” для виртуальных окружений.
poetry env use python
Creating virtualenv my-project-PSaGJAu6-py3.9 in /Users/alex.mitelman/Library/Caches/pypoetry/virtualenvs Using virtualenv: /Users/alex/Library/Caches/pypoetry/virtualenvs/my-project-PSaGJAu6-py3.9
Хэш проекта остается неизменным, а версия Python изменилась на 3.9.
Имеем новое окружение, и надо теперь переустановить все зависимости:
poetry install
На следующем этапе надо активировать виртуальное окружение и внимательно проверить, что выбрана правильная версия Python. Итак, запускаем VS Code из виртуального окружения, и VS Code теперь может найти новое окружение (надо еще не забыть закрыть VS Code перед этим).
poetry shell python -V code .
И последний этап, в левом нижнем углу нажми на версии Python, и выбери нужную версию в списке. Проверь, что выбрал версию именно виртуального окружения.
Не забывай обновлять отдел [tool.poetry.dependencies] в файле pyproject.toml, чтобы корректно отображалась поддержка версий Python.
VS Code предложит выбрать линтер, форматировщик кода и прочее. Игнорируй. Сначала настрой описанное выше, остальное – потом (далее я опишу, как и что).
Можно все это сделать и альтернативным путем, командой venv.
Как тестировать код при помощи pytest
Зачем нужен pytest
Тесты нужны, тесты важны, поэтому первым делом после создания проекта позаботимся о тестах.
pytest – популярный фреймворк, широко одобряемый в Python-сообществе. Он так популярен, что идет в комплекте с Poetry как средство тестирования. Итак, открываем раздел [tool.poetry.dev-dependencies] в pyproject.toml, и видим, что pytest уже в списке зависимостей.
Poetry прописывает за нас структуру тестовых папок.
cd tests; ls
__init__.py __pycache__ test_my_project.py
Как работать с pytest в VS Code
Очень простой пример. Создаем и протестируем функцию, умножающую два числа. В соответствии с TDD сначала создаем тест.
Откроем test_my_project.py и добавляем импорт функции, а также простой тест:
from my_project.math import multiply_two_numbers def test_multiply_two_numbers(): result = multiply_two_numbers(2, 3) assert result == 6
Вместо запуска наших тестов из терминала, сделаем все в редакторе (на примере MacOS). Нажимаем комбинацию ⇧⌘P, далее начинаем вводить “Python: Discover Tests” и выбираем нужное в списке.
Тестовый фреймворк появился в правом нижнем углу. Нажимаем его:
и выбираем pytest в выпадающем списке:
На последнем этапе, надо задать папку, где будут наши тесты:
VS Code нашел наши тесты, и добавил кнопки для запуска и дебага тестов.
Так как мы работаем в VS Code, редактор выделит их красным, потому что эти функции еще не прописаны.
Итак, создаем файл math.py в папке my_project и далее пропишем нашу простую функцию:
def multiply_two_numbers(a, b): return a * b
Когда запустим тесты снова, они будут уже зелеными:
Альтернативный путь: запустить тесты из командной стройки:
poetry run pytest
или из активированного виртуального окружения, командой:
pytest
Или так: unittest
– инструментом, включенным в стандартный дистрибутив Python. Но есть минусы: “не питоновский” camelcase API, неудобный синтаксис.
Как оценить покрытие тестами при помощи pytest-cov
Если есть тесты, надо знать покрытие ими кода. Можно поставить плагин pytest-cov для pytest
:
poetry add --dev pytest-cov
Теперь запуск тестов с дополнительным параметром будет генерировать отчет по покрытию кода тестированием:
pytest --cov=my_project tests/
================================================= test session starts ============= platform darwin -- Python 3.9.2, pytest-5.4.3, py-1.9.0, pluggy-0.13.1 rootdir: /Users/alex/iCloud/dev/projects/my-project plugins: cov-2.10.1 collected 2 items tests/test_my_project.py .. [100%] ----------- coverage: platform darwin, python 3.9.2 ----------- Name Stmts Miss Cover -------------------------------------------- my_project/__init__.py 1 0 100% my_project/math.py 4 0 100% -------------------------------------------- TOTAL 5 0 100% ================================================== 2 passed in 0.08s ===============
Также сгенерируется файл .coverage, которого мы не хотим видеть в нашем контроле версий, поэтому не забудь добавить его в соответствующий списочек .gitignore
:
echo '.coverage' > .gitignore
Как провести проверку до отправки на предварительный коммит
В Git
Мы еще не затрагивали здесь контроль версий, а теперь займемся этим. Конечно, в 2021 году все работают в Git. Установить последнюю версию (или обновить) в MacOS:
brew install git
Если в проекте еще нет репозитория, надо его создать. Github создает новый репозиторий по умолчанию с веткой main и делаем это:
git init -b main
Сначала уточним, мы должны иметь у себя файл .gitignore
, и он не даст невпопад коммитить “временные” файлы или бинарники в наш Git-репозиторий. Можно вручную скопипейстить отсюда или просто запустить команду создающую .gitignore, и загрузить контент из указанной ссылки. При этом надо находиться в корневой папке проекта.
curl -s https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore >> .gitignore
Можно исключить папку настроек VS Code из контроля версий. Также можно исключить папку настроек PyCharm, если кто-то в проекте работает в этом IDE.
echo '.vscode/\n.idea/' >> .gitignore
Теперь добавим все нужное в контроль версий:
git add .
Зачем запускать проверку перед коммитами?
Как ты уже понял (и еще увидишь дальше), объем проекта большой. Легко что-то упустить перед тем как отправить код в репозиторий. В результате, код не проходит автоматическую проверку качества непрерывной интеграции кода (CI), твои коллеги или тестировщики могут заставлять корректировать код. Иногда все тормозится из-за того, что кто-то не поставил пробел или символ конца строки, а ведь это тоже входит в стандарт качества POSIX например, в чем можно убедиться по ссылке. Даже если помнишь все мелкие стандарты, это ни от чего не спасает, все равно легко ошибиться. Но есть удобный инструмент избежать “залипания” в этих легко автоматизируемых мелочах.
Для этого и существует pre-commit
. Это средство автоматической проверки каждого git-коммита. Оно простое и не требует рут-доступа. Написано на Python, но разумеется может работать в проектах с другими языками.
Установка и работа pre-commit
pre-commit может ставиться на системном уровне, но мы так делать не будем, именно потому, почему применяем pyenv – у нас много разных версий Python, и наши зависимости надо хранить в порядке. Мы ставим pre-commit
как зависимость:
poetry add pre-commit --dev
Далее мы “прикрепим” все наши “проверяльщики” (линты) и подобные инструменты для предварительной проверки кода. Создаем простой конфиг для проверки на наличие в положенном месте – символа конца строки (EoF, newline), о котором речь шла выше.
Есть команда в pre-commit для быстрого создания такого конфига. В корневой папке вводим:
pre-commit sample-config > .pre-commit-config.yaml
Создан небольшой файл конфигурации, содержащий такие строки:
# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files
Здесь видно, что наш pre-commit уже сам прописал некоторые удобные хуки. Теперь смотрим, как оно работает. Запуск проверки вручную:
pre-commit run --all-files
После запуска pre-commit, становится понятно, что даже в файлах, автоматически создаваемых в VS Code, нет в конце этого символа (newline), между прочим требуемого стандартом!
Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Failed - hook id: end-of-file-fixer - exit code: 1 - files were modified by this hook Fixing my_project/math.py Fixing .vscode/settings.json Fixing tests/test_my_project.py Check Yaml...............................................................Passed Check for added large files..............................................Passed
А pre-commit пофиксил эту проблему за тебя, так что если запустить ту же команду еще раз, все проверки покажут ОК.
Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Passed Check Yaml...............................................................Passed Check for added large files..............................................Passed
Самая важная часть тут – правильно “связать” Git с этими хуками, и запускать их перед каждым коммитом.
pre-commit install
pre-commit installed at .git/hooks/pre-commit
Теперь мы выполняем коммиты (то есть передаем свои правки на Git):
git commit -m 'Initial commit'
Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Passed Check Yaml...............................................................Passed Check for added large files..............................................Passed [master (root-commit) 7dc335f] Initial commit 11 files changed, 559 insertions(+) create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .python-version create mode 100644 .vscode/settings.json create mode 100644 README.rst create mode 100644 my_project/__init__.py create mode 100644 my_project/math.py create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 tests/__init__.py create mode 100644 tests/test_my_project.py
Хуки запускаются перед коммитом, как положено.
И последнее, можно заставить pre-commit периодически обновлять конфиги к последней версии:
pre-commit autoupdate
И он немедленно обновил хук, в моем случае:
Updating https://github.com/pre-commit/pre-commit-hooks ... updating v2.4.0 -> v3.2.0.
Анализ кода в линтере Flake8
Почему линтеры нужны в проектах на Python
Линтеры – твой заслон от багов и тупых ошибок-опечаток. Они сигнализируют об ошибках заблаговременно, до передачи кода на выполнение. Конечно же, все IDE-редакторы поддерживают в себе линтеры, и их не нужно запускать отдельно. Редактор с подключенным линтером подсвечивает некорректный код прямо после ввода.
В “Мире Python’а” существует много линтеров, основных два – Flake8 и Pylint.
Начнем с Pylint, это строгий и дотошный линтер. С ним работают люди в Google, в их Python-проектах, и это предусмотрено в их гугловских гайдлайнах. Из-за дотошности этого линтера, возможно, придется тратить время на разбор всех подробностей, и настройку срабатывания. Что, в принципе, неплохо – учишься писать удобочитаемый правильный код сразу же, “не отходя от кассы”. Безопасный код, однако, учиться писать намного дольше, как показывает опыт.
Популярнейший линтер с открытыми исходниками – Flake8. Но перед тем как начать, надо обсудить одну вещь.
Python Style Guide- PEP8
Гайдлайн по Питону, известен по номеру “Предложений по Улучшению №8” – PEP8. Все программисты на Питоне попросту обязаны ознакомиться с PEP8, и понятно, с Дзеном тоже (РЕР20).
После прочтения РЕР8, достаточно утомительного документа, возникает вопрос, а есть ли “тулз” автоматической проверки, и приведения в действие всех этих указаний? Это делает Flake8, и не только это. Он работает сразу после установки, и может настраиваться на специфические правила у каждого разработчика.
Flake8 не такой строгий как Pylint, и вполне сносно “несет службу на первом краю”, где твоя безалаберность обычно наивысшая.
Поэтому советую все-таки работать с Flake8. Можно иметь в запасе и Pylint как запасной вариант, но в Flake8 работать обязательно.
Как ставится Flake8
VS Code обязательно спросит при установке, оп, а какой будет линтер? На Маке нажимаем ⇧⌘P, дальше вводим “linter”, выбираем в списке “Python: Select Linter”.
И выбираем “flake8”
VS Code подхватит виртуальное окружение и установит Flake8 как зависимость.
Если так не получилось, или если хочется поставить Flake из командной строки:
poetry add flake8 --dev
Как работать в Flake8
Итак, вернемся к коду в предыдущей главе. VS Code подсвечивает красным ошибки, с точки зрения линтера. Подводим мышку к ошибке, видим сообщение с номером и описанием ошибки (можно тут же посмотреть в интернете, о чем это), и название линтера.
Можно запускать Flake8 вручную:
flake8 .
./tests/test_my_project.py:4:1: E302 expected 2 blank lines, found 1 ./tests/test_my_project.py:7:1: E302 expected 2 blank lines, found 1
Как “подвязать” Flake8 к хукам Git?
О красной подсветке ошибок в IDE иногда забывают, так же как о запуске команды flake8 перед отправкой кода в репозиторий. Поэтому существуют хуки. Добавим Flake8 в список хуков, и ко будет проверяться автоматически.
Для этого откроем файл .pre-commit-config.yaml
и добавляем следующее:
- repo: https://gitlab.com/pycqa/flake8 rev: 3.8.3 hooks: - id: flake8
Теперь внимательно – на строчку с rev. Здесь не обязательно версия 3.8.3. Узнать текущую версию Flake8 можно, открыв pyproject.toml, там найти версию Flake8, и скопировать-вставить ее в конфиг-файл пре-коммита, как показано выше.
Помни: мы раньше заставили pre-commit обновлять версии, поэтому Flake8 будет постоянно обновляться к последней версии. Но мы уже используем самую новую версию линтера, и ничего не будет происходить. pre-commit загрузит эту версию Flake8, потому что он проводит свои проверки в отдельном окружении. Это значит, что у нас получится две разных версии Flake8, и надо убедиться, что сейчас работает правильная. Обычно обновление к последней версии не составляет проблем,но если они возникают, советую отключить автоматическое обновление для pre-commit.
После редактирования конфиг-файла делаем так:
git add .pre-commit-config.yaml
И передаем его:
git commit -m 'Add Flake8 to git hooks'
Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Passed Check Yaml...............................................................Passed Check for added large files..............................................Passed flake8...............................................(no files to check)Skipped [master 1d25c9f] Add Flake8 to git hooks 1 file changed, 4 insertions(+)
Давайте попробуем вручную запустить pre-commit еще раз, чтобы проверить, как работает Flake8 в связке с Git:
pre-commit run --all-files
Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Passed Check Yaml...............................................................Passed Check for added large files..............................................Passed flake8...................................................................Failed - hook id: flake8 - exit code: 1 tests/test_my_project.py:4:1: E302 expected 2 blank lines, found 1 tests/test_my_project.py:7:1: E302 expected 2 blank lines, found 1
Не спеши фиксить эту ошибку, чтобы “снять” ее из линтера. Надо пробовать фиксить ее в автоматическом режиме.
Форматирование кода в Black
Каждый программист обладает собственным стилем. Даже на Питоне, который принудительно заставляет делать отступы, можно писать код по своему.
Иногда, глядя на кусок кода, я могу сказать, кто из моей команды писал его. Когда код пишут одновременно несколько человек, он становится “лоскутным”.
Как этого избежать: по идее, перед написанием кода надо посоветоваться, иначе дело замедлится. Вообще же, пререкания насчет стиля – есть дело личного вкуса. В конце рабочего дня люди ссорятся о мелочах, о том как внешне выглядит код, а не о том как он работает.
Круто, когда код одинаково выглядит во всем проекте, как если бы его писал один человек. Есть такое решение, такой инструмент, он называется Black.
Этот софт форматирует код “в одном ключе”, и код по крайней мере внешне – выглядит однообразно. В Black можно настраивать мало что, но здесь не тот случай, когда есть выбор. Просто поставь его, и пользуйся без претензий. В конце концов, РЕР8 и Black создавались для “причесывания” всего под единый образец, и тут уж ничего не поделать, претензии излишни.
Сразу заметно, что сам Black игнорирует правило из РЕР8 о длине строки. Вообще, в РЕР8 сказано, что строка может быть максимально длиной в 79 символов. Это имеет исторические корни, удивительно, от перфокарт IBM и первых UNIX-терминалов. РЕР8 писали в далеком 2001 году, с того времени много чего изменилось. Люди начали ставить под сомнение это правило. “Не читаю код на UNIX-терминале”, говорят они. “У меня 27-дюймовый IPX-монитор”, говорят они. Однако, есть и люди, которые пишут код на маленьком 13-дюймовом лэптопе. И, например, искать глазами разницу между двумя текстовыми файлами, с длиной строки всего 79 символов – им довольно удобно. Представить только, горизонтальную перемотку огромного файла с большой длиной строки на таком экране. Поэтому “правило 79 символов” сохранено.
И да, хотя РЕР8 составлен в 2001, он неоднократно обновлялся (список новшеств), и все-таки правило, еще раз обсудив, не трогали.
И это правило Black дает нам настроить.
Как поставить Black
В VS Code открываем Python-файл в нашем новом проекте. Например test_my_project.py, о котором так беспокоится наш Flake8. Нажимаем и вводим “format”, дальше выбираем “Format Document”.
Всплывает диалог выбора форматировщика.
Выбираем форматировщик: “Use Black”.
VS Code обнаружит, что уже есть Poetry, и автоматически поставит Black.
Или же можно поставить вручную, командой:
poetry add --dev black --allow-prereleases
Опция –allow-prereleases присутствует, потому что Black все еще бета (весной-летом 2021 по крайней мере). Когда-то в 2019 думали, что закончат с бетами в том же году, но как говорится. Впрочем, многие серьезные продакшены и опенсорсные проекты “причесывают” свой код с Black, поэтому можно считать, что этот “дефолтный” форматировщик – в достаточной мере стабилен и надежен.
Чтобы настроить Black, открываем pyproject.toml и вставляем следующий раздел:
[tool.black] line-length = 79 target-version = ['py38'] include = '\.pyi?$' exclude = ''' ( /( \.eggs # exclude a few common directories in the | \.git # root of the project | \.hg | \.mypy_cache | \.tox | \.venv | _build | buck-out | build | dist )/ | foo.py # also separately exclude a file named foo.py in # the root of the project ) '''
Самая важная часть здесь – установить, что максимальная длина строки равна 79.
Если работаешь и в других версиях Python, то обновляй их тоже, но убедись, что они поддерживаются в Black.
Также понадобится “притушить” некоторые ошибки в Flake8, чтобы Flake8 нормально работал с Black. Для этого создаем конфиг-файл setup.cfg (то есть файл конфигурации Flake8), и вставляем там это:
[flake8] extend-ignore = E203
Как работает Black
Очень просто он работает. Запускается и форматирует код:
black .
reformatted /projects/my-project/my_project/__init__.py reformatted /projects/my-project/tests/test_my_project.py All done! ✨ 🍰 ✨ 2 files reformatted, 2 files left unchanged.
Flake8 жаловался на отсутствие символов перевода строки? (об этом шла речь выше.) Запускаем Git-хук, и видим что все ОК.
pre-commit run --all-files
Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Passed Check Yaml...............................................................Passed Check for added large files..............................................Passed flake8...................................................................Passed
Как добавить Black в git-хуки?
Так же как Flake8, работа Black отлично автоматизируется, так что не волнуемся. Вставляем в конфиг .pre-commit-config.yaml следующий кусок:
- repo: https://github.com/psf/black rev: 20.8b1 hooks: - id: black
Опять запускаем pre-commit run –all-files. Видим что загружается Black, и работает:
[INFO] Initializing environment for https://github.com/psf/black. [INFO] Installing environment for https://github.com/psf/black. [INFO] Once installed this environment will be reused. [INFO] This may take a few minutes... Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Passed Check Yaml...............................................................Passed Check for added large files..............................................Passed flake8...................................................................Passed black....................................................................Passed
Сохранение изменений в Git описывать не буду, это одинаковое с Flake8.
Есть еще альтернатива – yapf.
Бонус!
Можно сделать в VS Code вертикальную линейку (ruler), показывающую тот самый лимит 79 символов. Открываем settings.json
в папке .vscode и вставляем туда:
"[python]": { "editor.rulers": [72, 79] }
Линейка будет видна только в питон-коде. В docstrings будет только 72 символа.
Статические типы в Mypy
Применение проверки статических типов в Python
Споры о типах полыхали еще в 1970-е. IBM со своим Smalltalk – против Sun с их Java. Как мы знаем, выиграла Java и концепция “strong-type”, однако и Smalltalk не исчез со своим динамическим типированием; это рассматривалось как преимущество, по крайней мере в некоторых крупных компаниях, так как этим обеспечивалась быстрота разработки. Конечно, строгое типирование хранит от многих багов и runtime-исключений; но от них можно уберечься и 100%-ным покрытием тестами (как считает Uncle Bob, это и есть главная причина успешности Python). Но давайте также не забывать, что не все проекты можно 100% покрыть тестами.
А можно заиметь лучшее из обеих концепций. Можно соединить безопасные типы, и быстроту разработки. И в этом поможет Mypy – инструмент анализа статического кода, на предмет его безопасности (type-safe check). Проект превратился в такой ценный, что на него обратил внимание сам Его Величество творец Python Гвидо ван Россум, и добавил аннотации по типам в Python, и присоединился к разработке Mypy. Аннотации типов делают код удобочитаемым, а также удобным для IDE в плане быстроты авто-дополнения кода.
Как установить mypy
В VS Code открываем файл settings.json в папке .vscode. Устанавливаем параметр “python.linting.mypyEnabled”: в положение :true. Затем открываем любой питон-файл из проекта, например тот же math.py. VS Code сразу сообщает, что линтер Mypy еще не установлен.
Нажимаем “Install”, и Mypy поставится автоматически вместе с Poetry.
Или же, можно поставить Mypy вручную:
poetry add --dev mypy
Как работает Mypy
Теперь надо опробовать этот “тулз” в своем проекте.
mypy .
Success: no issues found in 4 source files
Вроде как все хорошо, у нас даже не надо ничего корректировать, Mypy автоматически проверяет типы.
Как упоминалось выше, аннотации типов помогают лучше читать код и понимать его, и IDE лучше авто-дополняет код, так что давайте пробовать. Открываем конфиг-файл setup.cfg и вставляем туда этот кусок:
[mypy] follow_imports = silent strict_optional = True warn_redundant_casts = True warn_unused_ignores = True disallow_any_generics = True check_untyped_defs = True no_implicit_reexport = True disallow_untyped_defs = True ignore_missing_imports = True
Тут самая важная строчка – disallow_untyped_defs = True. Она заставляет тебя “описывать функции типами”, то есть принудительное присвоение типов. В существующих старых проектах это можно отключить, но у нас новый проект, поэтому будет полезно убедиться, что аннотации типов не пропущены.
Может понадобиться временно отключить Mуpу для тестирования, для этого вставляем следующий кусок в настройки:
[mypy-tests.*] ignore_errors = True
Еще лучшую совместимость дают плагины для Mypy. К примеру, если нужно применять pydantic для валидации данных или их сериализации, конфиг-файл будет содержать такие строчки:
[mypy] plugins = pydantic.mypy follow_imports = silent strict_optional = True warn_redundant_casts = True warn_unused_ignores = True disallow_any_generics = True check_untyped_defs = True no_implicit_reexport = True disallow_untyped_defs = True ignore_missing_imports = True [pydantic-mypy] init_forbid_extra = True init_typed = True warn_required_dynamic_aliases = True warn_untyped_fields = True
После вставки такого куска, на выводе получим:
my_project/math.py:1: error: Function is missing a type annotation Found 1 errors in 1 files (checked 4 source files)
Помимо этого, в VS Code Mypy работает в качестве линтера, подчеркивая ошибки:
Чтобы пофиксить их, вставляем аннотации типов в нашу функцию:
from numbers import Real from typing import Union def multiply_two_numbers(a: Union[int, Real], b: Union[int, Real]) -> Union[int, Real]: return a * b
Как видишь, описание функции слишком длинное. Нажимаем ⇧⌥F, и VS Code отформатирует код при помощи Black, сократив длину строки до 79 символов:
from numbers import Real from typing import Union def multiply_two_numbers( a: Union[int, Real], b: Union[int, Real] ) -> Union[int, Real]: return a * b
Коммитим внесенные правки:
git commit -m 'Add Mypy'
Как добавить mypy в список git-хуков
Надо проверить, что Mypy запускается до коммита кода. Вставляем в файл .pre-commit-config.yaml следующий кусок:
- repo: https://github.com/pre-commit/mirrors-mypy rev: v0.782 hooks: - id: mypy additional_dependencies: [pydantic] # add if use pydantic
Вводим команду pre-commit run –all-files, она устанавливает запуск Mypy для предварительных коммитов и проводит проверки:
[INFO] Installing environment for https://github.com/pre-commit/mirrors-mypy. [INFO] Once installed this environment will be reused. [INFO] This may take a few minutes... Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Passed Check Yaml...............................................................Passed Check for added large files..............................................Passed flake8...................................................................Passed black....................................................................Passed mypy.....................................................................Passed
Сортирование импортов при помощи isort
Ну и последнее. Импорты.
Зачем вообще сортировать импорты?
РЕР8 говорит, что импорты должны быть отсортированы, причем в следующем порядке: стандартная библиотека, third party библиотека, локальная библиотека. Кроме того, импорты должны быть “красивыми, хорошо читаемыми”.
Есть и такой инструмент! Называется isort, как бы сокращение от “import sort”. Здесь по ссылке описание.
До сортировки импорты выглядят так:
from my_lib import Object import os from my_lib import Object3 from my_lib import Object2 import sys from third_party import lib15, lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11, lib12, lib13, lib14 import sys from __future__ import absolute_import from third_party import lib3
После – так:
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
Лучше, правда?
Как поставить isort, добавить в список git-хуков и работать с ним
VS Code (с установленным Python-расширением) запускает isort “автоматически”, так что не надо какой-то дополнительной настройки. Если ты не будешь работать с isort из командной строки, то даже не надо отдельно устанавливать его, потому что pre-commit автоматически устанавливает все зависимости в окружениях.
Но, если понадобится работать с isort вне VS Code и pre-commit, то устанавливать надо так:
poetry add --dev isort
Чтобы isort корректно “сотрудничал” с Black, в pyproject.toml надо добавить строчки:
[tool.isort] multi_line_output = 3 include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true line_length = 79
В VS Code на Маке нажимаем ⇧⌘P, вводим “sort imports”, и увидим:
Или же, если isort уже установлен, вводим, находясь в корневой папке проекта:
isort .
Skipped 2 files
Ну и добавляем isort в список хуков в конфиг-файле .pre-commit-config.yaml:
- repo: https://github.com/PyCQA/isort rev: 5.4.2 hooks: - id: isort
Вот как оно работает например с pre-commit run –all-files:
Trim Trailing Whitespace.................................................Passed Fix End of Files.........................................................Passed Check Yaml...............................................................Passed Check for added large files..............................................Passed flake8...................................................................Passed black....................................................................Passed mypy.....................................................................Passed isort....................................................................Passed
Fast Track
Вот как можно создать полностью сконфигурированный проект за пару минут (если pyenv и poetry уже установлены):
poetry new my-project; cd my-project; ls pyenv local 3.9.2 poetry env use python poetry add --dev pytest-cov pre-commit flake8 mypy isort poetry add --dev --allow-prereleases black poetry shell code .
Добавляем настройки в pyproject.toml:
[tool.isort] multi_line_output = 3 include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true line_length = 79 [tool.black] line-length = 79 target-version = ['py38'] include = '\.pyi?$' exclude = ''' ( /( \.eggs # exclude a few common directories in the | \.git # root of the project | \.hg | \.mypy_cache | \.tox | \.venv | _build | buck-out | build | dist )/ | foo.py # also separately exclude a file named foo.py in # the root of the project ) '''
Создаем setup.cfg:
[flake8] extend-ignore = E203 [mypy] follow_imports = silent strict_optional = True warn_redundant_casts = True warn_unused_ignores = True disallow_any_generics = True check_untyped_defs = True no_implicit_reexport = True disallow_untyped_defs = True ignore_missing_imports = True [mypy-tests.*] ignore_errors = True
Создаем .pre-commit-config.yaml:
repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - repo: https://gitlab.com/pycqa/flake8 rev: 3.8.4 hooks: - id: flake8 - repo: https://github.com/psf/black rev: 20.8b1 hooks: - id: black - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.812 hooks: - id: mypy additional_dependencies: [pydantic] # add if use pydantic - repo: https://github.com/PyCQA/isort rev: 5.7.0 hooks: - id: isort
echo '.coverage' > .gitignore echo '.vscode/\n.idea/' >> .gitignore curl -s https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore >> .gitignore git init -b main git add . git commit -m 'Initial commit' pre-commit install pre-commit autoupdate pre-commit run --all-files pre-commit run --all-files
Итоги
Сегодня ты потрудился на славу, можно поздравить, если добрался сюда. Конечно, это было утомительно. Перед нами стояла цель: настроить свой IDE и Python так, чтобы создание нового проекта требовало несколько минут. Если все сделать как описано выше, так и будет.