Pythonライブラリ開発にpysenを導入してみた
OSS の Python ライブラリ開発をする機会があり、コードを綺麗に保つ環境を整えた方が良いだろうということで、 pysen を導入してみました。
この記事では、 pysen をしばらく使ってみた感想を書きました。
Pythonのlinterとformatter
Python Enhancement Proposal(PEP)
という Python に関するドキュメント集があり、そのうち、8 番目の PEP 8
が Style Guide for Python Code について書かれています。コーディング規約はこの PEP 8 を基準に考えられているそうです。
https://www.python.org/dev/peps/
linter
このコーディング規約を支援するツールあり、linter はコーディング規約を守っているかチェックするツールです。
代表的なものとしては次のようなものがあります。
上位 4 つはコーディングスタイルや論理的なエラー[1]を検出するのに対し、mypy は PEP 484, PEP 526 での型ヒントに関するチェックを行います。
- pycodestyle (旧 pep8)
- pyflake
- flake8 (pycodestyle + pyflake のラッパー)
- pylint
- mypy (PEP 484, PEP 526 型ヒント)
formatter
formatter はコーディング規約を満たすようにコードを整えるツールです。
上位 3 つはコーディングスタイルを自動的に修正し、 isort は import の順番のみを修正するツールとなっています。
- autopep8
- yapf
- black
- isort (import の順番を修正)
linterとformatterの選び方は…
どれを使えばよいかについては、特に決まりはありません。大事なことは linter や formatter を利用してコードを整えておくことです。
どのツールも PEP 8 を基準にしていますが、それぞれで独自のコーディング規約を持っているものがあり、好みが分かれるポイントでもあるようです。
個人的には、black のフォーマットに flake8 を合わせる形が好みです。black と合わないところは flake8 の Warning を出ないように設定する感じです。pysen を使い始めたことをきっかけに、これらに加えて isort + mypy を使いはじめました。
コマンドの使い方、configの書き方…
flake8 と black を組み合わせて利用する際に抑制する Warning は何か、その設定をどう書くのかを調べるだけでも結構がかかります。
flake8 の場合は、今なら .flake8
, setup.cfg
, pyproject.toml
などの候補があり、プロジェクトによって様々です[2]。
linter、formatter の実行時にどのような引数を与えると良いのかについても、知らなくても出来るのであれば、導入コストが下がって良さそうです。
という背景でpysenは作られているらしい
設定項目が散乱しがちな linter と formatter を一元管理して導入コストを抑えられるところに共感し、今回導入するきっかけになりました。
使い方
基本的な使い方
README や紹介記事内でも触れられていますが、ここでも簡単に紹介しておきます。
pysen は flake8 + isort + mypy + black を利用し、pip install 時に同時に取得することが出来ます。
pip install "pysen[lint]"
次に、pysen 用の設定を pyproject.toml
に記述しておきます。この内容は README に記載されていたものです。
[tool.pysen]
version = "0.9"
[tool.pysen.lint]
enable_black = true
enable_flake8 = true
enable_isort = true
enable_mypy = true
mypy_preset = "strict"
line_length = 88
py_version = "py37"
[[tool.pysen.lint.mypy_targets]]
paths = ["."]
pyproject.toml
を配置したら、後は以下のコマンドで formatter や linter を実行できます。
pysen run format
pysen run lint
CI
GitHub Actions であれば以下のように 1 コマンドで linter によるチェックを統合出来ます。
format 後に差分が発生したり、lint error が発生した場合は error code で 1 を返してくれるので、そのまま書けば OK です。
- name: Check format & lint
run: pysen run lint
この例ではエラーは発生していませんが、実行結果がとても分かりやすく気に入っています。
他のエディタとの連携
さっきのコンフィグはあくまでも pysen 用でした。VSCode など、他のエディタで使うには各 linter 用の設定が必要になります。
これは以下のコマンドで解決できます。pysen で管理している設定を pyproject.toml
や setup.cfg
に出力出来るので、VSCode であれば勝手に読み込んでいい感じにしてくれました。
pysen generate .
また、VSCode 拡張機能として pysen-vscode
も用意されており、flake8 や mypy の実行結果をエディタ上に表示することが出来るようです。
Vim や Emacs への統合方法についても README で説明されていました。
使う時に気にしたこと
今回記事を書こうと思ったきっかけがここからの内容です。
pysen は記事執筆日(2021/9/25)時点での最新バージョンであった 0.9.1 を対象としています。
追記: バージョン 0.10.0 のリリースに伴い、 GitHub リポジトリの README.md
が更新され、 Frequently Asked Questions のセクションが追加されたようです。
Q. pysen seems to ignore some files.
の項目もありますね。(ありがとうございます)
Git 管理されているファイルがflake8やformatterのターゲットとなる
コミット前に format を確認するためにコマンドを実行するわけですが、なぜかフォーマットのターゲットとならないことがありました。
この例では、run_files
で直接ファイル指定していますが、それでも Skip されてしまっています。
少し、コードを読み込んでみました。 Skip されているところはこの 42 行目のようです。
更に読んでいくと、 Git で追跡されているかどうかをチェックしている部分が見つかりました。
結果としては、 flake8 や formatter は Git 管理下にあるファイルのみがターゲットとなる、が正解でした。関係ないファイルの lint エラーが表示されなくなり便利なのですが、気が付きにくい仕様だったので、ドキュメントに書いておいてくれると嬉しいです。
とりあえず、flake8 の lint、isort や black の format のターゲットになっていないような気がしたら、git add
しているか確認してみると良さそうです[3]。
ただし、mypyは別。
ただし、mypy の lint は Git で管理されていても、ターゲットにはならないらしく、別に記述する必要がありました。
[[tool.pysen.lint.mypy_targets]]
paths = [
".",
"./tests/"
]
pysen で実行される mypy の正確な仕様を確認したわけではありませんが、経験的には以下のようなルールがありました。ドキュメントが無く、実験から推測した結果で、正確ではない可能性があります。
- カレントディレクトリ内のファイル
- Python ライブラリのモジュールとして認識出来るディレクトリ(
__init__.py
が配置されているディレクトリ)内のファイル -
./tests
ディレクトリ内のファイル
また、どちらもサブディレクトリは対象にはなりません。例えば、./tests/package1/
にあるファイルは対象になりません。もし、そのサブディレクトリに __init__.py
があれば 2. のルールで対象になります。
mypy用のpyiファイルに対するlint
mypy 用に型ヒントを記述する *.pyi
ファイルに対しては、 無視してほしい Warning が *.py
とは異なります。
そこで、 flake8 の per-file-ignores
を利用して、以下のように記述することで実現出来ます。
[flake8]
per-file-ignores =
*.py: E203,E231,E501,W503
*.pyi: E301,E302,E305,E701,E704,E741
しかし、pysen では ignore
にしか対応していません。
mypy を利用するにあたって、こういった需要は必ず出てきます。そこで、pysen を導入しているリポジトリを調査したところ、pysen 向けの plugin を書いて利用していることが見つかりました。
VSCodeとの連携
per-file-ignores の設定
先程の per-file-ignores
の plugin は、まだ setup.cfg
に自動で書き出すことは出来ないようでした。正確には、 ignore
の項目として出力されます。そのため、自分で書き直す必要があります。
一度、pysen generate .
で設定を書き出した後、以下のように修正しました。
[flake8]
-ignore = E203,E231,E501,W503
+# ignore = E203,E231,E501,W503
+
+per-file-ignores =
+ *.py: E203,E231,E501,W503
+ *.pyi: E301,E302,E305,E701,E704,E741
VSCode 向けの拡張機能として pysen-vscode
が用意されていると紹介しました。しかし、 pysen-vscode
は VSCode の Python 拡張機能である ms-python
と競合するために、ms-python
を無効にする必要があります。
これについては、他の方も言及されていました[4][5]。
ms-python
を無効にするということは、インテリセンスやデバッグ機能が使えなくなるということなので、利用は厳しいように感じました。今はコマンドラインからの実行だけで pysen を利用しています。
普段は ms-python
を利用し、あるプロジェクトだけ pysen-vscode
を利用するという方法を一応考えてみたので紹介しておきます。実現方法としては、あるワークスペースに対して拡張機能を無効/有効にする機能を利用します。
- まず、
pysen-vscode
の拡張機能のページから、グローバルで無効化する。 -
pysen-vscode
を使いたいプロジェクトを VSCode で開き、名前をつけてワークスペースを保存から、ワークスペースを作成する。 - 作成したワークスペースを開き、
pysen-vscode
をワークスペースに対して有効化、ms-python
をワークスペースに対して無効化する。
次回からは、ディレクトリを開く代わりにワークスペースを開くようにすれば OK です。また、ms-python
を無効にしたことで、 Python インタプリタを VSCode から変更出来なくなります。
指定したい場合は、使いたい Python 仮想環境を事前に activate した状態でコマンドラインから VSCode を開き、そこからワークスペースを開き直すようにすれば出来ました。
source ./venv/bin/activate
code .
debugログがログしていない
今回の問題を解決するために、pysen の挙動を確認しようと思い、debug ログを出力しようとしました。しかし、肝心の部分が見切れてしまっています。
省略しないで…みたいな気持ちになりました。
再びコードを眺めてみました。
どうやら、文字数制限がつけられているようでした。debug ログぐらい全部出力したいのですが、何か問題でもあったのでしょうか…[6]。
おわり
導入はめちゃめちゃ簡単ですし、実行結果がまとまっていて見やすいところがとても気に入っていて、導入していないプロジェクトにはおすすめしたいツールだと感じました。
ですが、今回のような細かい部分が惜しいです。結局 pysen の挙動を理解するコストが出てしまったので、pysen を使って linter や formatter の知識が増えれば、それぞれのコマンドを呼び出す方向への切り替えも検討しています。その方が PFN 外部の人が使う面では、ドキュメントや参考プロジェクトが多く、楽そうと感じてしまいました...。
これから先の改善を楽しみにしています。
Discussion