📌 はじめに
Pythonで開発を行うにあたり、リンタやフォーマッタ、パッケージマネージャ等のツールの選定は非常に重要な問題です。一方で歴史的な経緯もあり、沢山の選択肢から何を選ぶべきか情報がまとまっていないように感じました。この記事では2021年9月時点でモダンと言えるであろう開発環境を紹介します。基本的にはシェアが高いこと、著名なパッケージで使用されていることを主な選定理由としており、また特定のエディタに依存しないことを前提とします。
本記事で紹介する内容は一つのテンプレートに近く、必要に応じてカスタマイズするもよし、そのまま使ってもよし、として参考になればと思います。(CI/CDについてはPythonとは独立した問題なので触れません。またドキュメント生成はSphinx
を推しますが、必須ではないので今回は割愛します。)
📄 要約
"モダン"な開発環境を箇条で列挙すると下記の通りです。以降、それぞれのツールについて触れます。
- Python
- CPython:
3.9
- CPython:
- Tools
- Package manager: poetry
- Formatter: black
- Import sorter: isort
- Linter: pflake8
- Plugin: flake8-bugbear
- Type checker: mypy
- Git hook manager: pre-commit
- Code format
- Max line length:
119
- Max line length:
オプショナルなツール類
- 複数バージョンのPythonを扱うときなど
- CI/CDの補助
- Automated dependency updater:
Dependabot
- Code coverage manager:
codecov
(*)
- Automated dependency updater:
(*) 2021年4月に不正アクセスを受けていたことを公表しています。
🔖 "モダン"な開発環境の概要
便利かつ最新の仕様を反映しているツールを選定するがまず第一です。加えて、pyproject.tomlに設定類/依存関係を集約すること が一つのポイントです。pyproject.toml
はPEP518
にて定義された仕様で、より具体的には従来requirements.txt
やsetup.py
といったファイルに散乱していた情報を一箇所に集めることができよりスマートになります。リポジトリのルートディレクトリはゴチャゴチャしがちなので、整理されていると嬉しいですよね。
参考
✅ Python: CPython
: 3.9
Pythonの実装は様々ありますが、特に理由がなければオリジナルの実装であるCPython
(誤解を恐れずに言えば"普通のPython")を使うのが無難でしょう。
バージョンについては3.6
系のサポートは2021-12-23
にEoLを迎えるため、3.7
系かそれより新しいバージョンを使用するべきです。加えて、パッケージの依存関係等で制約がない限りは基本的に新しいバージョンを使用することをおすすめします。
補足: 3.10
系が2021-10-04
にリリース予定です。ただしリリース直後は各種パッケージが未対応であることがあるため、しばらくは3.9
系を使うのが無難かもしれません。
また、オプショナルなツールとして、Pythonのバージョン管理にはpyenv
(≠pyvenv)を使うことをおすすめします。(Node.jsでいうところのn
に該当します) ※複数のPythonバージョンとパッケージの取り扱い、仮想環境についてはPEP582
で議論されていますが、現時点ではまだDraftです。
[参考リンク]
- devguide.python.org/#status-of-python-branches
- 2020年における各Python処理系の状況
- PEP582 - Python local packages directory
✅ パッケージ管理: Poetry
poetry
はNode.jsでいうところのnpm
やyarn
、rustでいうところのcargo
に相当するパッケージマネージャの一つです。比較対象としてpipfile
やrust製で後発のpyflow
やが挙げられますが、現時点ではpoetry
がよいでしょう。これらの比較については既に素敵な記事を書かれてる方がいらっしゃるので参考欄に記載します。
poetry | pipfile | pyflow |
---|---|---|
[参考リンク]
✅ フォーマッタ: black
black
は非常に制約の強いフォーマッタです。裏を返すとコードスタイルに幅がないがゆえにシンプルで、その分の考えるリソースを別の問題に割くことができます。
多くの有名なライブラリにも採用されており、前述のpoetry
や機械学習等で使われるpandas
なども、black
によってフォーマットされています。
black
を使用するにあたっては競合を避けるため、他のツール類に設定が必要な場合があります。本記事で紹介している組み合わせについては各項で取り上げますが、その他については参考欄に公式のDocリンクを貼ります。また、本記事下部の [ max line length: 119
]についても合わせてご覧ください。
[参考リンク]
✔️ インポートのソート: isort
isort
は文字通りインポート周りをソート(フォーマット)してくれるツールです。black
はインポート文の順序までは規定がないため、isort
と組み合わせて使うことでより快適になります。デフォルトのisort
の設定ではblack
と競合するため、profile = "black"
として設定を行う必要があります。
✅ リンタ: pflake8
flake8
は最も有名なリンタの一つです。しかし唯一の難点は設定ファイル.flake8
をpyproject.toml
に統合できないことです。議論(#issues/234)はされていますが、いくつかの理由で実現に至っていません。
そこでflake8
をラップしpyproject.toml
から設定を読むようにしたものがpflake8
です。正直ここは好みが分かれる部分だと思いますが、本記事ではリポジトリのルートディレクトリの美しさを優先しました。Startsを見てオリジナルのflake8
を選択するのも良いと思います。
補足1: black
との競合を避けるためE203: Whitespace before ':'
は無視する設定を行うことが推奨されています。
補足2: pflake8
と同様の機能を提供するパッケージは他にも2つ(flake9
, flakehell
)見受けられましたが、pyproject.toml
への設定統合以上の追加機能も提供しており、一般的な開発には過剰と判断しました。
flake8 | pflake8 |
---|---|
[参考リンク]
✔️ プラグイン: flake8-bugbear
そもそもflake8はコードのエラーチェック、PEP8の遵守チェック、循環的複雑度のチェックをまとめて行ってくれるツールです。これに加えてバグや問題が起きそうなところの警告を行うようにしてくれるのがflake8-bugbear
です。適用したいルールは選択可能なので、一覧を眺めて選ぶもよし、とりあえず全部有効にするもよし、です。
[参考リンク]
✅ タイプチェッカ: mypy
Pythonは型を明示することなくコーディングをすることが可能ですが、可読性や保守性を考えると型を明示するべきです。1ファイルに収まる程度の短いスクリプトやNotebookを書くなら別ですが、ある程度の規模のコードを書くならばType hints
を添えることの恩恵は大きいです。
mypy
は静的に型チェックを行うライブラリの一つで、最も古株、そしてベーシックです。Type checkerは現時点で事実上のデファクトスタンダードとなるものがないように思われ、恐らくその要因は用途や目的、そして使用するエディタによって使われているものが異なるためです。(web系ならpyre
、vscode
使いならpyright
を含む拡張であるpylance
、など)
本記事では最もベーシックかつ依存関係の小さいmypy
を推奨としますが、参考まで下記に同様の機能を提供するライブラリとの簡潔な比較を記載します。
-
mypy
(python.org 製)- ✨: 必要十分な機能
- 🤔: 大規模になると若干遅さを感じる場合がある
-
pyright
(Microsoft製)- ✨:
mypy
の5倍高速 - 🤔: インストールに
npm
を要し、前提となる要素が増える
- ✨:
-
pytype
(Google製)- ✨: 型が明示されていないコードは型推論によって検証する
- 🤔: 型が明示されていないコードで矛盾がなければ、問題が問題とみなされないことがある
-
pyre
(Facebook製)- ✨: Web周りのセキュリティのチェックも行ってくれる(
pysa
の機能) - 🤔: インストールに
watchman
を要し、前提となる要素が増える
- ✨: Web周りのセキュリティのチェックも行ってくれる(
[参考リンク]
* 4 Python type checkers to keep your code clean
* 多くのPythonコードに型アノテーションしてみたので色々所感を書いてみる
✅ Gitフックマネージャ: pre-commit
Gitフックを管理するためのフレームワークがpre-commit
です。.pre-commit-hooks.yaml
というファイルに設定することで、commit時などにスクリプトが実行されます。スクリプトは自分で書くこともできますが、github上に公開されているものが数多くありますので、それらを指定することで簡単に様々なチクを設定できます。Hookの例を下記に示します。またblackやflake8などのツール類もHookを提供していますので、コミット前にフォーマット、リンティングを自動で行うと安心です。
- repo: https://github.com/pre-commit/pre-commit-hooks
- hooks:
- id: trailing-whitespace # 文末の空白を除去
- id: end-of-file-fixer # ファイルの末尾が改行か確認
- id: check-json # Jsonファイルの形式を確認
- id: check-yaml # Yamlファイルの形式を確認
- hooks:
✅ コーディング規約
基本的にはblack
を使用することでPEP8
を遵守することとなりますが、PEP8
には一部モダンではない仕様が含まれています。またblack
も一行の長さ制限についてひとクセあるため明記します。
✔️ max line length: 119
PEP8
では一行の文字数は79
文字以内と規定されています。これは諸説ありますが、1行が80文字のエディタ(ディスプレイ)を想定し、勝手に折り返されることを避けることが目的だとされています。(79文字+改行コード=80文字)
しかし現代のディスプレイサイズを考えればこの79
という数字は不適切で、あるいはType hints
を書こうと思うとあまりに短すぎます。ではいくつに設定するのが良いのか?という問題の一つの答えが119
です。これはGitHubのCode Review機能で一行に表示される最大の文字数に由来します。
補足: (あまり発生しない事象ですが)black
は設定した一行の長さを守ろうと努力しますが、他のルールを破ってまでは守りません。下記のようなコメントがドキュメントに記載されており、そのためにflake8
のE501
(line too long)を無効にしてflake8-bugbear
のB950
(max line lengthを10%超過したら初めて咎める、スピード違反的な取締り方式)を推奨しています。が、上記の通りGitHubのCode Review機能に追従して119
文字と設定するなら、一文字でも長いと困るのでE501
を使用するのが良いと思います。
“try to respect --line-length, but don’t become crazy if you can’t”
blackのドキュメントから引用
ところで若干脱線しますが、blackのドキュメントでは視覚障害のある方は1行が100文字以上だと見にくいということが言及されています。W3Cのドキュメントにおいても視覚障害と一行の長さによる見やすさ(見にくさ)は言及されていますので、プロジェクトメンバに合わせて適切な値を設定すると良いと思います。
📎 開発環境のサンプル
上記の環境を構成した状態のディレクトリと構築に要したコマンド、そしてpyproject.toml
のサンプルを以下に示します。
./my-package/
├── .pre-commit-hooks.yaml
├── .venv/
├── README.md
├── my_package/
├── poetry.lock
├── pyproject.toml
└── tests
poetry new my-package
poetry add -D black
poetry add -D pyproject-flake8
poetry add -D flake8-bugbear
poetry add -D isort
poetry add -D mypy
poetry add -D pre-commit
# Add some configurations to pyproject.toml with editor
[tool.poetry]
name = "my-package"
version = "0.1.0"
description = ""
authors = ["example <example@example.com>"]
[tool.poetry.dependencies]
python = "^3.9"
[tool.poetry.dev-dependencies]
black = "^21.7b0"
pyproject-flake8 = "^0.0.1-alpha.2"
mypy = "^0.910"
isort = "^5.9.3"
pre-commit = "^2.15.0"
flake8-bugbear = "^21.9.1"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
# Following configurations are added manually
[tool.flake8]
max-line-length = 119
max-complexity = 10
select = "C,E,F,W,B"
ignore = "E203"
[tool.black]
line-length = 119
exclude = '''
(
migrations
| .mypy_cache
| .pytest_cache
| .tox
| .venv
| dist
)
'''
[tool.mypy]
# common
python_version = 3.9
show_column_numbers = true
show_error_context = true
ignore_missing_imports = true
check_untyped_defs = true
disallow_untyped_defs = true
# warning
warn_return_any = true
warn_unused_configs = true
warn_redundant_casts = true
[tool.isort]
profile = "black"
line_length = 119