Ruffで快適なPython環境を構築する
まえがき
みなさんこんにちは、こんばんは、最近業務でまたPythonを書くことになったつっちーです。動的型付け言語は好きになれないのですが、Pythonってなんやかんや需要ありますよね。ということで新たにPythonの開発環境を構築した際に、割と便利だったRuffの紹介です。
Ruff ??
静的コード解析やフォーマットで従来から使われていた、Flake8やBlackなどを統合したツールです。Rust製で高速に動作するらしいです(筆者はRust推し)。
なぜRuff ??
オールインワンこそ正義!
従来の構成ですと、フォーマッターにBlack
、リンターにFlake8
、isort
、型注釈するのであればmypy
を使いますし、Flake8
もラッパーなので内部的にはPyFlakes
、pycodestyle
、mccabe
などと、とにかく使うツールが多い印象です。
あれっオールインワンじゃないのでは?
Ruffで全て完結すれば幸せになれるのですが、Ruffはリンターであり型チェッカーではないと公式に明示しているため、現状はMypy等と組み合わせて使うのがプラクティスのようです。
Ruff is a linter, not a type checker. ...
It's recommended that you use Ruff in > conjunction with a type checker, like Mypy, ...
https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-mypy-or-pyright-or-pyre
やってみた
Ruffは各ルールを識別子で指定します。統合されたツールだけあって、ルールは700以上にも及びます。今回は以下のルールを設定して検証してみようと思います。
https://docs.astral.sh/ruff/rules/
区分 | ルール | 識別子 | 備考 |
命名ルール | pep8-naming | N | PEP8に準拠した命名ルール |
コードスタイル | pycodestyle | E, W | PEP8に準拠したコーディングルール |
ドキュメント文字列 | pydocstyle | D | PEP257, Google, Numpy等に準拠したdocstringスタイルを強制する |
論理エラー | Pyflakes | F | 論理エラーを検知する |
インポートソート | isort | I | インポート順をソートする |
複雑度チェック | mccabe | C90 | 複雑度をチェックする |
セキュリティチェック | flake8-bandit | S | セキュリティチェック(※Flake8プラグイン) |
設定ファイルはpyproject.toml
またはruff.toml
に記述できます。内容はシンプルでなかなか良きです。
select = ["N", "E", "W", "D", "F", "I", "C90", "S"]
[lint.mccabe]
max-complexity = 3
実践
コードを書いて検証してみます。全てのエラーが出るようなコードを書くのが逆に大変でした(笑)
# main.py
import os, sys
import io
def tooBadFunction(a, b, c):
"""ダメな関数のdocstring"""
eval(sys.argv[1])
if a:
if b:
if c:
return 1
else:
return 2
else:
return 3
return 4
チェックしてみます。
$ ruff main.py
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
main.py:1:1: E401 Multiple imports on one line
main.py:1:1: D100 Missing docstring in public module
main.py:1:1: I001 [*] Import block is un-sorted or un-formatted
main.py:1:8: F401 [*] `os` imported but unused
main.py:2:8: F401 [*] `io` imported but unused
main.py:5:5: N802 Function name `tooBadFunction` should be lowercase
main.py:5:5: C901 `tooBadFunction` is too complex (4 > 3)
main.py:6:5: D202 [*] No blank lines allowed after function docstring (found 1)
main.py:6:5: D400 First line should end with a period
main.py:6:5: D415 First line should end with a period, question mark, or exclamation point
main.py:8:5: S307 Use of possibly insecure function; consider using `ast.literal_eval`
Found 11 errors.
[*] 4 fixable with the `--fix` option (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
さすがに少しうるさいですね。docstring周りのルールを少し緩めたいと思います。日本語で書いているのに末尾のピリオドとか言われても困ってしまいますよね。
Ruffではエラーコード単位でルールを除外することができます。
select = ["N", "E", "W", "D", "F", "I", "C90", "S"]
ignore = ["D203", "D212", "D400", "D415"]
[lint.mccabe]
max-complexity = 3
ちなみにdocstringはGoogleスタイルでやらせていただいているんですけど、という場合
select = ["N", "E", "W", "D", "F", "I", "C90", "S"]
ignore = ["D415"]
[lint.pydocstyle]
convention = "google"
[lint.mccabe]
max-complexity = 3
良さそうです。詳細をコメントしてみます。
$ ruff main.py
# 単一行での複数インポート
main.py:1:1: E401 Multiple imports on one line
# モジュールレベルのdocstringが記述されていない
main.py:1:1: D100 Missing docstring in public module
# インポートブロックがソートされていない
main.py:1:1: I001 [*] Import block is un-sorted or un-formatted
# 未使用インポート
main.py:1:8: F401 [*] `os` imported but unused
main.py:2:8: F401 [*] `io` imported but unused
# 関数名がキャメルケース(PEP8だとPythonはスネークケース)
main.py:5:5: N802 Function name `tooBadFunction` should be lowercase
# 複雑度が閾値(3)を超えている
main.py:5:5: C901 `tooBadFunction` is too complex (4 > 3)
# docstringの後は空行を入れる(PEP257)
main.py:6:5: D202 [*] No blank lines allowed after function docstring (found 1)
# evalはセキュリティ的にNG
main.py:8:5: S307 Use of possibly insecure function; consider using `ast.literal_eval`
Found 9 errors.
[*] 4 fixable with the `--fix` option..
自動化したい
やっぱ書いている側から検知して欲しいし、保存時には整形して欲しいですよね。
安心してください、IDEのプラグインを入れれば検知してくれますし、Ruffはフォーマッターも兼ねているので保存時に整形もしてくれます。
設定例はVSCodeですがIntellijなどでも同様のことができます。
# .vscode/settings.json
{
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "always",
"source.organizeImports.ruff": "always"
},
"editor.formatOnSave": true,
},
}
所感
オールインワンと言ってもあくまでインターフェースの部分であって、各ツールの仕様を把握していないと少し設定しづらいかなという印象です。設定項目が多すぎて紹介できませんでしたがフォーマットルールなども細かく設定できますし、何より導入コストが低いことが好印象でした。皆さんも煩雑になりがちなリンター/フォーマッター設定から解放されてみてはいかがでしょうか。