nikkie-ftnextの日記

イベントレポートや読書メモを発信

flake8(pycodestyle)には、二項演算子と改行について、排他的なルールがある(W503とW504)

はじめに

枕木の歌、いいよね... nikkieです。

flake8を触っていての気づきを書いていきます。

目次

相互に排他的な W503 と W504

静的解析ツールflake8。
PEP 8(Pythonコードのスタイルガイド)に沿っているかもチェックしてくれます。
PEP 8関連のワーニングの中に今回取り上げたいものがあります

まずは、W503!

二項演算子で改行しているのをよしとし、前で改行している場合はその旨を指摘します。
以下は二項演算子+の後で改行しているのでW503的には問題なし

income = (gross_wages +
          taxable_interest)

続いて、W504!

二項演算子で改行しているのをよしとし、後で改行している場合はその旨を指摘します。
以下は二項演算子+の前で改行しているのでW504的には問題なし

income = (gross_wages
          + taxable_interest)

この2つ、絶対に両立しないですね(=相互に排他的)

2つある経緯は、PEP 8の変更

PEP 8に二項演算子と改行に関する箇所があります。
https://peps.python.org/pep-0008/#should-a-line-break-before-or-after-a-binary-operator

PEP 8自体は2001年に作られたドキュメントですが、なんとこの箇所の記載は変わったようなのです!

  • 当初は、二項演算子で改行を勧めていた
  • 2016年4月に議論があり、二項演算子で改行を勧めるように変更

Guido-san「PEPを変えよう!」1
(この意思決定に至った経緯はメーリングリストを追ってみたい気持ちです)

PEP 8を変えても、二項演算子の後で改行がよいとされた15年程度の期間に書かれたコードは残っています。
PEP 8はそれを排除しようとせずに、新しいコードは二項演算子の前で改行するのを勧めています2

二項演算子の後で改行しているコードも許容しているのがいいなと思います3

In Python code, it is permissible to break before or after a binary operator, as long as the convention is consistent locally.

大事なのは、スタイルに一貫性があること!(consistent)

顕在化するのは、--ignoreオプションと使った時

flake8は3つのライブラリをラップしており、PEP 8のチェックはpycodestyleが担っています。

ドキュメント Introductionの「Error codes」より
https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes

  • W503とW504はデフォルトの設定では無視されます4
  • --ignore=errorsと指定すると、デフォルトの設定が上書きされます5
    • W503やW504を--ignoreオプションで指定しなければ無視されなくなります

検証していきます

  • Python 3.11.8
  • pip install flake8
    • flake8 7.0.0
    • pycodestyle 2.11.1
# W503を満たすが、W504に違反
income = (gross_wages +
          taxable_interest)

# W504を満たすが、W503に違反
income = (gross_wages
          + taxable_interest)

--ignoreを指定せずデフォルトの設定を使うと、エラーは検出されません。

% pycodestyle script.py
% echo $?
0

--ignoreを指定すると

% pycodestyle --ignore=E401 script.py
script.py:2:23: W504 line break after binary operator
script.py:7:11: W503 line break before binary operator
% echo $?
1

検出され、コマンドとしては異常終了しました

また、flake8ではヘルプメッセージに記載されています6

  --ignore errors       Comma-separated list of error codes to ignore (or
                        skip). For example, ``--ignore=E4,E51,W234``.
                        (Default: E121,E123,E126,E226,E24,E704,W503,W504)

ここから、pycodestyle(やflake8)で--ignoreを指定するときは、デフォルトのignore設定の上書きになることに注意し、W503かW504(か両方)を指定する必要があると理解しました。

終わりに

flake8(pycodestyle)に相互に排他的なルール(W503・W504)を見つけ、気になって調べました。

  • PEP 8の二項演算子と改行のスタイルガイドは更新された
    • かつてはW503。二項演算子の後で改行を推奨していた
    • 2016年の議論を機に、二項演算子の前で改行推奨に変更(W504)
    • 二項演算子の後で改行したコードを排斥しようとはしていない(過去へのリスペクト、エモい)
  • W503もW504もデフォルトでは無視されているが、ignoreオプションの指定で上書きしてしまう
    • IMO:新しいコードを書く場合は、おすすめされなくなったW503もignoreに指定しよう

本エントリの元ネタです

P.S. Ruffでは?

エントリ執筆時点では、W503もW504も実装されていません。
https://docs.astral.sh/ruff/rules/#warning-w

pycodestyleがデフォルトで無視しているルールというのが実装していない理由とのことです7
https://github.com/astral-sh/ruff/issues/4125#issuecomment-1524657579


  1. メールは Update handling of breaking around binary operators: W503 vs W504 · Issue #498 · PyCQA/pycodestyle · GitHub で知りました
  2. For new code Knuth’s style is suggested.」(PEP 8より)
  3. setup.pyとpyproject.tomlっぽいと思いました。
  4. 原文「(*) In the default configuration, the checks E121, E123, E126, E133, E226, E241, E242, E704, W503, W504 and W505 are ignored
  5. 原文「Please note that if the option --ignore=errors is used, the default configuration will be overridden and ignore only the check(s) you skip.
  6. 代わりに--extend-ignoreを指定すると、これはデフォルトのignoreに追加するようです
  7. Ruffは速いのですが、未実装のルールがあるなど、完全互換ではないのですね(違いの認識が必要そう)