Ruffで快適なPython環境を構築する

tsucci

まえがき

みなさんこんにちは、こんばんは、最近業務でまたPythonを書くことになったつっちーです。動的型付け言語は好きになれないのですが、Pythonってなんやかんや需要ありますよね。ということで新たにPythonの開発環境を構築した際に、割と便利だったRuffの紹介です。

Ruff ??

静的コード解析やフォーマットで従来から使われていた、Flake8やBlackなどを統合したツールです。Rust製で高速に動作するらしいです(筆者はRust推し)。

https://docs.astral.sh/ruff/

なぜRuff ??

オールインワンこそ正義!

従来の構成ですと、フォーマッターにBlack、リンターにFlake8isort、型注釈するのであればmypyを使いますし、Flake8もラッパーなので内部的にはPyFlakespycodestylemccabeなどと、とにかく使うツールが多い印象です。

あれっオールインワンじゃないのでは?

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-namingNPEP8に準拠した命名ルール
コードスタイルpycodestyleE, WPEP8に準拠したコーディングルール
ドキュメント文字列pydocstyleDPEP257, Google, Numpy等に準拠したdocstringスタイルを強制する
論理エラーPyflakesF論理エラーを検知する
インポートソートisortIインポート順をソートする
複雑度チェックmccabeC90複雑度をチェックする
セキュリティチェックflake8-banditSセキュリティチェック(※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,
    },
}

所感

オールインワンと言ってもあくまでインターフェースの部分であって、各ツールの仕様を把握していないと少し設定しづらいかなという印象です。設定項目が多すぎて紹介できませんでしたがフォーマットルールなども細かく設定できますし、何より導入コストが低いことが好印象でした。皆さんも煩雑になりがちなリンター/フォーマッター設定から解放されてみてはいかがでしょうか。

AUTHOR
tsucci
tsucci
記事URLをコピーしました