LoginSignup
1
1

More than 1 year has passed since last update.

Ruff, Flake8, Pylintでの"Line too long"エラーに関する結果の違い

Last updated at Posted at 2023-05-05

環境

$ ruff --version
ruff 0.0.264

$ flake8 --version
6.0.0 (mccabe: 0.7.0, pycodestyle: 2.10.0, pyflakes: 3.0.1) CPython 3.11.2 on Linux

$ pylint --version
pylint 2.17.3
astroid 2.15.4
Python 3.11.2 (main, Mar  2 2023, 15:11:26) [GCC 11.3.0]

はじめに

PythonのリンターをFlake8からRuffに移行した際、1行の長さが長すぎないかのチェック("Line too long")の結果が微妙に異なっていました。
どのようなときに結果が異なるかを、Flake8とRuffに加えPylintで確認しました。

設定ファイル

行の最大長さを11文字としました。

pyproject.toml
[tool.ruff]
select = ["E501"]
line-length = 11

[tool.pylint]
max-line-length = 11
disable = "all"
enable = "line-too-long"
.flake8
[flake8]
select = E501
max-line-length = 11
lint.sh
#!/bin/bash -x
LINT_FILE=$1
ruff check ${LINT_FILE}
flake8 ${LINT_FILE}
pylint ${LINT_FILE}

"Line too long"の結果

文字列の場合

sample.py
"1234567890"
"12345678 0"
"12345678 0"

sample.pyのすべての行が12文字です。2行目は半角スペース、3行目は全角スペースを含んでいます。

$ ./lint.sh sample.py

+ ruff check sample.py
sample.py:2:12: E501 Line too long (12 > 11 characters)
sample.py:3:11: E501 Line too long (13 > 11 characters)
Found 2 errors.

+ flake8 sample.py
sample.py:1:12: E501 line too long (12 > 11 characters)
sample.py:2:12: E501 line too long (12 > 11 characters)
sample.py:3:12: E501 line too long (12 > 11 characters)

+ pylint sample.py
************* Module sample
sample.py:1:0: C0301: Line too long (12/11) (line-too-long)
sample.py:2:0: C0301: Line too long (12/11) (line-too-long)
sample.py:3:0: C0301: Line too long (12/11) (line-too-long)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)

Ruffでは、11文字を超えていてもスペースを含まなければ、"Line too long"のエラーになりませんでした。

以下は、Ruffの判定処理です。行をこれ以上短くできない場合は、エラーにならないようです。

ruff/src/rules/pycodestyle/helpers.rs
    let mut chunks = line.split_whitespace();
    let (Some(first_chunk), Some(second_chunk)) = (chunks.next(), chunks.next()) else {
        // Single word / no printable chars - no way to make the line shorter
        return None;
    };

コメント行の場合

sample1.py
# "34567890"
# "345678 0"
# "345678 0"

sample1.pyのすべての行が12文字です。2行目は半角スペース、3行目は全角スペースを含んでいます。

$ ./lint.sh sample1.py
+ ruff check sample1.py
sample1.py:1:12: E501 Line too long (12 > 11 characters)
sample1.py:2:12: E501 Line too long (12 > 11 characters)
sample1.py:3:11: E501 Line too long (13 > 11 characters)
Found 3 errors.

+ flake8 sample1.py
sample1.py:2:12: E501 line too long (12 > 11 characters)
sample1.py:3:12: E501 line too long (12 > 11 characters)

+ pylint sample1.py
************* Module sample1
sample1.py:1:0: C0301: Line too long (12/11) (line-too-long)
sample1.py:2:0: C0301: Line too long (12/11) (line-too-long)
sample1.py:3:0: C0301: Line too long (12/11) (line-too-long)```

1行目は#の後にスペースがあるため、Ruffではエラーになりました。
しかし、Flake8ではエラーになりませんでした。

以下は、Flake8(実際の処理はpycodestyle)の判定処理です。
docstringやコメント行にURLのような長い文字がが存在するケースでは、エラーにならないようです。

pycodestyle.py
        # Special case for long URLs in multi-line docstrings or
        # comments, but still report the error when the 72 first chars
        # are whitespaces.
        chunks = line.split()
        if ((len(chunks) == 1 and multiline) or
            (len(chunks) == 2 and chunks[0] == '#')) and \
                len(line) - len(chunks[-1]) < max_line_length - 7:
            return

#の後ろに半角スペースがないコメント行の場合

あまり見かけないスタイルではありますが、#の直後に半角スペースが存在しないコメント行でも確認しました。

sample.py
#"234567890"
#"2345678 0"
#"2345678 0"
$ ./lint.sh sample2.py
+ ruff check sample2.py
sample2.py:2:12: E501 Line too long (12 > 11 characters)
sample2.py:3:11: E501 Line too long (13 > 11 characters)
Found 2 errors.

+ flake8 sample2.py
sample2.py:1:12: E501 line too long (12 > 11 characters)
sample2.py:2:12: E501 line too long (12 > 11 characters)
sample2.py:3:12: E501 line too long (12 > 11 characters)

+ pylint sample2.py
************* Module sample2
sample2.py:1:0: C0301: Line too long (12/11) (line-too-long)
sample2.py:2:0: C0301: Line too long (12/11) (line-too-long)
sample2.py:3:0: C0301: Line too long (12/11) (line-too-long)

Ruffでは、1行目はスペースが含まれていないので1単語とみなされて、エラーになりませんでした。
Flake8では、1行目はエラーになりました。Flake8のソースコードを見ると、chunks = line.split()でスペースで分割した後、最初のchunkが#かどうかを判定しています。1行目はスペースが含まれていないため、chunk[0]#"234567890"となり、chunks[0] == '#'がTrueになるため、エラーになったようです。

docstringの場合

2行目、6行目、10行目は12文字です。

sample3.py
"""
1234567890ab
"""

"""
12345678 0aa
"""

"""
12345678 0ab
"""
$ ./lint.sh sample3.py

+ ruff check sample3.py
sample3.py:6:12: E501 Line too long (12 > 11 characters)
sample3.py:10:11: E501 Line too long (13 > 11 characters)
Found 2 errors.

+ flake8 sample3.py
sample3.py:6:12: E501 line too long (12 > 11 characters)
sample3.py:10:12: E501 line too long (12 > 11 characters)

+ pylint sample3.py
************* Module sample3
sample3.py:2:0: C0301: Line too long (12/11) (line-too-long)
sample3.py:6:0: C0301: Line too long (12/11) (line-too-long)
sample3.py:10:0: C0301: Line too long (12/11) (line-too-long)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)

Flake8では、2行目はエラーになりません。Flake8のソースコードを見ると、len(chunks) == 1 and multilineとい判定を行っています。この判定がTrueになるため、エラーにならないようです。

URLが存在する場合

1行目、2行目は11文字を超えています。

sample2.py
"https://qiita.com"
# "https://qiita.com"
$ ./lint.sh sample4.py

+ ruff check sample4.py

+ flake8 sample4.py
sample4.py:1:12: E501 line too long (19 > 11 characters)

+ pylint sample4.py
************* Module sample4
sample4.py:1:0: C0301: Line too long (19/11) (line-too-long)
sample4.py:2:0: C0301: Line too long (21/11) (line-too-long)

2行目は#の後ろにスペースがありますが、Ruffではエラーになりません。Ruffは、URLで終わる行はエラーにならないようにしています。

ruff/src/rules/pycodestyle/helpers.rs
    // Do not enforce the line length for lines that end with a URL, as long as the URL
    // begins before the limit.
    let last_chunk = chunks.last().unwrap_or(second_chunk);
    if last_chunk.contains("://") {
        if width - last_chunk.width() <= limit {
            return None;
        }
    }

マルチバイト文字を含む場合

以下のPythonファイルでは、1行目も2行目も12文字です。2行目にはマルチバイト文字が1文字含まれています。

sample5.py
"12345678 0"
"12345678 あ"
$ ./lint.sh sample5.py

+ ruff check sample5.py
sample5.py:1:12: E501 Line too long (12 > 11 characters)
sample5.py:2:12: E501 Line too long (13 > 11 characters)
Found 2 errors.

+ flake8 sample5.py
sample5.py:1:12: E501 line too long (12 > 11 characters)
sample5.py:2:12: E501 line too long (12 > 11 characters)

+ pylint sample5.py
************* Module sample5
sample5.py:1:0: C0301: Line too long (12/11) (line-too-long)
sample5.py:2:0: C0301: Line too long (12/11) (line-too-long)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)

Ruffでは、2行目は13文字だとみなされました。Ruffは、マルチバイト文字を1文字として扱わないようです。

ruff/src/rules/pycodestyle/helpers.rs
    for c in line.chars() {
        if width < limit {
            start_offset += c.text_len();
        }

        width += c.width().unwrap_or(0);
    }

Ruffのv0.0.260で、"Line too long"の計算が"character count"から"Unicode width"に変わりました。pycodestyleではなくblackに合わせたようです。

This PR changes the logic testing if a line exceeds the configured line-width to use the unicode-width rather than character count.
Changing this breaks compatibility with pycodestyle because they decided to stick with ASCII width (because PEP8 recommends ASCII source code). I think its worth diverging from pycodestyle because our formatter uses unicode width and black is adopting too

まとめ

Ruff, Flake8, Pylintで、"Line too long"のエラーの挙動を確認しました。

  • Ruff
    • 行が長くても、1単語ならばエラーにならない
    • 行が長くても、行の最後がURLならばエラーにならない
    • マルチバイト文字は1文字として扱わない
  • Flake8
    • docstringかどうか、コメント行かどうかで結果が変わる
  • Pylint
    • 単純に行の長さだけで判定している
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1