pylintとisortのケンカを仲裁する

TL;DR

  • isortとpylintのデフォルトの動作では、importの順番に意見の食い違いがある
  • その場合はisort -fssとすると、isort側がpylint型に歩み寄る
  • この動作をデフォルトにしたければ.isort.cfgに以下のように書く
[settings]
force_sort_within_sections=True

確認したバージョン

  • pylint 2.1.1 (Python 3.7.0)
  • isort 4.3.4

何が問題か

pylintはPythonコードがPEP8に従っているか確認するチェッカで、isortはPEP8に従ってimportの順番を並び替えてくれるプログラムだが、その意見が食い違う場合がある。

以下の3つのスクリプトを同じディレクトリに置く。

$ tree .
.
├── main.py
├── mod1.py
└── mod2.py

0 directories, 3 files
hoge1 = 1
  

def func1():
    print("mod1.func1")
hoge2 = 2


def func2():
    print("mod2.func2")
"""Pylint and isort compatibility"""
import mod1
from mod1 import func1
import mod2
from mod2 import func2

print(mod1.hoge1)
print(mod2.hoge2)
func1()
func2()

このとき、main.pyは、pylintで文句を言われない。

$ pylint main.py

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

しかし、isortは文句を言う。

$ isort -c main.py
ERROR: /path/to/main.py Imports are incorrectly sorted.

では、isortを実行して、isort好みに並び替えてみる。

$ isort main.py
Fixing /path/to/main.py

main.pyは最初にimport文、次がfrom hoge import fuga、という順番になる。

"""Pylint and isort compatibility"""
import mod1
import mod2
from mod1 import func1
from mod2 import func2

print(mod1.hoge1)
print(mod2.hoge2)
func1()
func2()

これにはpylintが文句を言う。

************* Module main
main.py:4:0: C0412: Imports from package mod1 are not grouped (ungrouped-imports)
main.py:5:0: C0412: Imports from package mod2 are not grouped (ungrouped-imports)

-------------------------------------------------------------------
Your code has been rated at 7.50/10 (previous run: 10.00/10, -2.50)

つまり、pylintはこうなっていて欲しい。

import mod1
from mod1 import func1
import mod2
from mod2 import func2

しかし、isortはこうなっていて欲しい。

import mod1
import mod2
from mod1 import func1
from mod2 import func2

どちらがPEP8準拠なのか? それを調べるため、我々はPEP8に飛んだ。・・・が、上記のどちらがPEP8準拠なのか、判断できなかった。

これは、例えばVimでALEを使っており、linterとしてpylint、fixerとしてisortを指定すると問題になる。保存するたびにisortが走り、その度にpylintが文句を言うからだ。

解決策

isortには-fssというオプションがある。これは--force-sort-within-sectionsの短縮形で、こういう意味がある。

Force imports to be sorted by module, independent of import_type

つまり、import_typeではなく、モジュール名を主キーとしてソートせよ、ということ。これにより、isort -fssは以下を吐き出す。

import mod1
from mod1 import func1
import mod2
from mod2 import func2

これはpylintは文句を言わない。

他の例

例えば、ChainerのMNISTを扱うサンプルtrain_mnist.pyの冒頭部分

#!/usr/bin/env python
import argparse
import re

import numpy

import chainer
import chainer.functions as F
import chainer.links as L
from chainer import training
from chainer.training import extensions
import chainerx

も、そのままisortをかけると

#!/usr/bin/env python
import argparse
import re

import chainer
import chainer.functions as F
import chainer.links as L
import numpy
from chainer import training
from chainer.training import extensions

import chainerx

となってしまい、pylintが文句を言う。isort -fssをかけると

#!/usr/bin/env python
import argparse
import re

import chainer
from chainer import training
import chainer.functions as F
import chainer.links as L
from chainer.training import extensions
import numpy

import chainerx

となり、オリジナルとはちょっと違うが少なくともpylintは文句を言わない。

まとめ

エディタがVimかどうかによらず、Pythonを組んでる人でautopep8だのpylintだのisortだので自動チェック、自動整形している人は多いと思う。調べた範囲ではpylintに歩み寄りのオプションは見つからなかったので、isortが歩み寄る必要がある。こんな簡単なことの解決に結構時間がかかってしまった。将来、pylintとisortの意見が食い違って困った人がこの記事にたどり着けば幸いである。