この記事 is 何? (TL;DR)
- Pythonのimport文の順序が人によってバラバラ問題を解決する
- isortを使ってimportをソートする
- pre-commitを使ってimport順序を守ってないコードはcommitできないようにする
Pythonのimport順序が規約で定まっていない問題
Pythonのimport順序については、PEP8に以下のように記載されてます。
- Imports are always put at the top of the file, just after any module
comments and docstrings, and before module globals and constants.
Imports should be grouped in the following order:
1. Standard library imports.
2. Related third party imports.
3. Local application/library specific imports.
You should put a blank line between each group of imports.
つまり、以下の順に空行を挟んで書けばOKなんですね!
- 標準ライブラリ
- サードパーティライブラリ
- 自作ライブラリ
・・・え?
標準ライブラリ同士での順番はどうしたらいいの?
from pathlib import Path
と import sys
はどっちが先なの?
実はPython規約では、標準ライブラリ同士での順序などについては決められていないのです!
つまり、プロジェクト内で統一する基準を決めてあげないと、人によってバラバラな書き方をしてしまうことになります!
isortを使った解決策
isortというツールを使用してプロジェクト内での統一ルールを決めて運用していきます。
-
pip install isort
でisortをインストールする - (デフォルト設定以外の設定で運用したい場合、)
~/.isort.cfg
ファイルを編集する -
isort -rc .
を実行して、プロジェクト内の全ファイルを定めたルールでソートする
デフォルト設定(.isort.cfgファイルを作成しない場合)だと以下のようにソートされるようになります!
デフォルトでもPEP8に沿ってソートしてくれていい感じです
import os
import sys
from pathlib import Path
import numpy as np
import pandas as pd
import boto3
import mylib1
import mylib2
import os
import sys
from pathlib import Path
import boto3
import numpy as np
import pandas as pd
import mylib1
import mylib2
おまけ:ソートされていないファイルをコミットできないようにする
pre-commitを使って、ソートされていないファイルをコミットできないようにしたい場合は以下のようにファイルを編集します!
参考: https://gist.github.com/acdha/8717683
-
cp .git/hooks/pre-commit.sample .git/hooks/pre-commit
してpre-commitファイルを作成する - pre-commitファイルを以下の内容で上書きする
※プロジェクトによって固有の値になりうる箇所をTODOとして記載している(2箇所)ので、そこを修正する必要があります。
※ which python
や which isort
の結果のパスを指定して上げればOKです。
#!/usr/bin/env PYTHONIOENCODING=utf-8 [TODO: プロジェクトのPython環境を記載する]
# encoding: utf-8
"""Git pre-commit hook which lints Python, JavaScript, SASS and CSS"""
from __future__ import absolute_import, print_function, unicode_literals
import os
import subprocess
import sys
FS_ENCODING = sys.getfilesystemencoding()
def check_linter(cmd, files, **kwargs):
if not files:
return
print('Running %s' % cmd[0])
return subprocess.check_output(cmd + files, stderr=subprocess.STDOUT, **kwargs).decode(FS_ENCODING)
def filter_ext(extension, files, exclude=None):
files = [f for f in files if f.endswith(extension)]
if exclude is not None:
files = [i for i in files if exclude not in i]
return files
def lint_files(changed_files):
changed_files = [i.strip() for i in changed_files.splitlines() if '/external/' not in i]
changed_extensions = {ext for root, ext in map(os.path.splitext, changed_files)}
if '.py' in changed_extensions:
py_files = filter_ext('.py', changed_files)
check_linter(['[TODO: プロジェクトのisortパスを記載する]', '-c'], py_files)
if '.js' in changed_extensions:
check_linter(['eslint'], filter_ext('.js', changed_files, exclude='.min.'))
if '.scss' in changed_extensions:
try:
check_linter(['scss-lint'], filter_ext('.scss', changed_files))
except subprocess.CalledProcessError as exc:
if exc.returncode == 1:
# scss-lint rc=1 means no message more severe than a warning
pass
else:
raise
if '.css' in changed_extensions:
check_linter(['csslint'], filter_ext('.css', changed_files, exclude='.min.'))
if __name__ == "__main__":
os.chdir(os.path.join(os.path.dirname(__file__), '..', '..'))
changed_files = subprocess.check_output('git diff --cached --name-only --diff-filter=ACM'.split())
changed_files = changed_files.decode(FS_ENCODING)
try:
lint_files(changed_files)
except subprocess.CalledProcessError as exc:
print('Quality check failed:', file=sys.stderr)
print(' '.join(exc.cmd), file=sys.stderr)
if exc.output:
output = exc.output.decode(FS_ENCODING)
print('\t', '\n\t'.join(output.splitlines()), sep='', file=sys.stderr)
sys.exit(1)
pre-commitファイルを作成して、isort順序が指定された方法でないファイルが有った場合、以下のエラーメッセージが出てcommitに失敗します。
コミットはエラーで失敗しました
0 file committed, 1 file failed to commit: [invalid!!]test commit
ERROR: /Users/xxx/PycharmProjects/isort_test/incorrect_isort.py Imports are incorrectly sorted.
上記のエラーにてコミットに失敗した場合は、 isort [ソートしたいファイルパス]
を実行してimport順序をソートすればcommitできるようになります!
おまけ:PyCharm使用者向け
PyCharmでいちいちコマンドでisortを実行したくない場合は、コマンドを外部ツールとして登録することができます!
Community版でも使用可能です!
参考: https://github.com/timothycrosley/isort/issues/258
- Pycharm > Preferences > External Tools > + を選択する
- ツールを編集する画面にて、以下の画像のように設定する
- importをソートしたいファイルを開いて、右クリック > External Tools > isort current file
※Pycharmを日本語化しているので一部画像が異なるかもしれません。
まとめ
The Zen of Pythonに則って、たったひとつの冴えたやり方をチームで模索していきましょう!
マサカリ大歓迎です!
There should be one-- and preferably only one --obvious way to do it.
何かいいやり方があるはずだ。誰が見ても明らかな、たったひとつのやり方が。
by The Zen of Python