環境
$ ruff --version
ruff 0.0.264
$ black --version
black, 23.3.0 (compiled: yes)
Python (CPython) 3.11.2
はじめに
Pythonのフォーマッターにblack、リンターにRuffを使用しています。
1行が長すぎる場合、RuffはE501
(Line too long)エラーを出力します。E501
を修正した後blackでフォーマットすると、再度RuffによりE501
エラーが出力されるケースがありました。
その現象をまとめます。
なお、RuffのIssueで報告されている内容と同じ現象です。
設定ファイル
1行の最大長さを20文字とします。
[tool.ruff]
select = ["RUF100", "E501"]
line-length = 20
[tool.black]
line-length = 20
どのような現象か?
1.最初の状態
print("あい うえお")
1行目は15文字です。全角文字を2文字としてカウントすると、21文字です。
$ black sample11.py
All done! ✨ 🍰 ✨
1 file left unchanged.
$ ruff check sample11.py
sample11.py:1:15: E501 Line too long (21 > 20 characters)
Found 1 error.
blackは「1行目は20文字を超えていない」と判断して、何もしません。しかしRuffは全角文字を2文字としてカウントするため、「1行目は20文字を超えている」と判断して、E501エラーとします。
2.Ruffでnoqaコメントを付ける
E501エラーを無視するため、行末にnoqa
ディレクティブを付けます。
$ ruff check sample11.py --add-noqa
Added 1 noqa directive.
print("あい うえお") # noqa: E501
1行目は29文字です。20文字を超えていて、かつ改行すれば20文字以下になるので、blackでフォーマット可能な状態になります。
$ black sample11.py --check
would reformat sample11.py
Oh no! 💥 💔 💥
1 file would be reformatted.
3.blackでフォーマット
$ black sample11.py
reformatted sample11.py
All done! ✨ 🍰 ✨
1 file reformatted.
print(
"あい うえお"
) # noqa: E501
blackでフォーマットすると改行されて、すべての行が20文字以下になりました。
4.再度Ruffでチェック
再度Ruffでチェックすると、「# noqa: E501
は不要」というエラーRUF100
が発生しました。
--fix
オプションを付けて、不要なnoqa
ディレクティブを取り除きます。
$ ruff check sample11.py
sample11.py:3:4: RUF100 [*] Unused `noqa` directive (unused: `E501`)
Found 1 error.
[*] 1 potentially fixable with the --fix option.
$ ruff check sample11.py --fix
Found 1 error (1 fixed, 0 remaining).
print(
"あい うえお"
)
2行目は13文字です。全角文字を2文字とカウントすると、19文字なのでRuffのE501エラーは発生しません。
5.再度blackでフォーマット
$ black sample11.py
reformatted sample11.py
All done! ✨ 🍰 ✨
1 file reformatted.
print("あい うえお")
blackでフォーマットすると1行になりました。改行を取り除いて1行にしても20文字以下になるためです。
そして、最初の状態に戻ってしまいました。
原因
以下の条件を全て満たしていると、「フォーマット→リンターによるチェック&修正→フォーマット→リンターによるチェック&修正→。。。」と、コードは常に変わってしまいます。
- (a) フォーマットツールにblackを使用している
- (b) リンターにRuffを使用している
- (c) Ruffはルール
E501
とRU100
をチェックする - (d) Ruffは1行の最大文字数を超えていると判断するが、blackは最大文字数を超えていないと判断する
- (e) 行末に
# noqa: E501
を付けると、blackは最大文字数を超えていると判断する - (f) blackでフォーマットした後は、Ruffは1行の最大文字数を超えていないと判断する
ポイントは条件(d)です。Ruffが全角文字を1文字としてカウントしてくれれば、blackと"line-length"のカウント方法は同じになります。Ruffとblackの"line-length"の設定が一致しているならば、条件(d)が満たさせることはありません。
Flake8の"line-length"はblackと同じ(全角文字を1文字としてカウントする)なので、このようにコードが常に変化することはありませんでした。
なぜRuffは全角文字を1文字としてカウントしないのか?
Ruffのv0.0.260から、line-length
の計算が"character count"から"Unicode width"に変更されました。
これは、blackの以下のPRに合わせたためです。以下のPRは、black v23.3.0でリリースされました。ただし"Unicode width"を使用するのはpreview版だけです。
以下は、blackの"line-length"の計算方法をpreview版と通常版で分岐している箇所です。
https://github.com/psf/black/blob/ef6e079901d53a42dfae4ab10b081ce7a73a47b5/src/black/lines.py#L763
解決策
blackのpreview版を利用する
最初の状態のPythonファイルに対して、--preview
オプションを指定してblackを実行すると、以下の状態になります。
5$ black sample11.py --preview
All done! ✨ 🍰 ✨
1 file left unchanged.
print(
"あい うえお"
)
フォーマット後の状態では、RuffはE501
エラーになりません。
Ruffのv0.0.260未満を利用する
Ruffのv0.0.260未満では、"line-width"の計算では全角文字を1文字としてカウントするので、blackの通常版と"line-width"の計算方法が同じになります。
RuffでルールE501
またRU100
をチェックしない
解決策というか回避策です。
"3.blackでフォーマット"後のときに、# noqa: E501,RUF100
を付与する
print(
"あい うえお" # noqa: E501,RUF100
)