🐍

2021年Python開発リンター導入のベストプラクティス

に公開
2

この記事は、Python に lint を導入するときに「まず回る最小構成」を決めて運用するためのメモです(2021年時点)。

  • 対象: Python プロジェクトで lint を導入したい人
  • この記事で分かること: おすすめ構成、最短導入、運用/無効化のやり方
  • 結論: black + isort + flake8 + mypytox でまとめて回します
  • 前提: アプリケーション開発を想定します(CI で lint を回します)
  • 限界: プロジェクト規模や厳密さで最適解は変わるので、まず最小で始めます

まずは次の「先に結論」だけ押さえ、必要なら「導入(最短ルート)」まで読み進めると迷いにくいです。

先に結論(おすすめ構成)

最小で効果が高いのは、black(整形)+ isort(import 整理)+ flake8(静的解析)+ mypy(型チェック)を入れて、tox でまとめて回す構成です。

まずは「1コマンドで lint が回る」状態を作り、困ったら「部分的な無効化」をチームで共有して詰まらないようにするのが続きます。

最短導入(tox 版)は次の順番が迷いにくいです。

  1. pip install tox
  2. tox.inisetup.cfg を置きます(例は本文の「導入」を参照してください)
  3. ローカル/CI は tox -e lint を共通コマンドにします
  4. 詰まったら「無効化」で一旦逃がします

この記事は何ですか?

Python 開発における

  • リンターの利点
  • 数ある Python リンターの違い・特徴
  • 導入方法
  • おすすめの設定
  • リンターを(部分的に)無効化する方法

を紹介しています。

執筆時点(2021年)の状況を踏まえた上で、多くのプロジェクトで適用できる構成になっていると思います。

リンターとは何ですか?

コードをチェック・解析するツールのことです。静的解析ツールとも呼ばれます。参考)lint (Wikipedia)
導入することで、以下の効果が得られます。

  • バグの発生が減ります
  • 保守性の高いコードを書けるようになります
  • チーム内でのコーディング規約(コードの書き方のルール)を自動的に守れるようになります
  • リンターが簡易自動テストのように働く場面もあります

つまり lint は「バグと揺れを減らす自動レビュー」として効きます。

本稿では「チェックするだけでなく、自動で整形までしてくれる」フォーマッターについても、リンターとして扱って紹介します。

リンターが導入されない理由(背景)

リンターは便利なもののようなのに、導入されていない Python プロジェクトは多くあります。
ここでは導入されないよくある理由を紹介します。

導入・設定方法が分からず調べるのも大変

  • 確かに npm 環境などに比べると Python でのリンター導入はわかりにくい印象です。そこで、この記事で紹介します。

リンターに合わせてコードを書くために逆に時間がかかる

  • 書いている時はそう思うかもしれませんが、コードは書く時間より読んで頭で整理する時間の方が圧倒的に多いです。特に他の人も読むコードはこの傾向が顕著で、時間をかけてでも良いコードを書くことが時短に繋がります。
  • また、すぐに無意識に書けるようになります。
  • 設定やオプションでチェックしない項目を設定することもできます。チームの有識者が最初にプロジェクトにあった形で設定しておけば、他のメンバーは何も気にしなくても良いコードが書けるようにリンターが導いてくれます。

時々どうしてもチェックが通らないパターンが出てきて、やめてしまった

  • Python のコメントを特別な形式で書くことで個別にスキップできます。この方法はチームメンバー全員に周知して利用していただくとよいです。
  • 特例として無効化すべき場合はもちろん、「より良い書き方がすぐには分からない」場合も積極的に利用してよいと思います。コメントが残るため検索しやすく、後から有識者が解決してくれることもあります。
  • あまりにも頻出するチェックが通らないパターンは、設定で全部オフにすることを検討します。

このセクションの要点

リンターが導入されない理由を潰す形で導入できる理由を述べてきましたが、とはいえやはりゼロからの導入には多少の時間はかかります。
プロジェクトが本当に簡易であったり、知識のあるメンバーがいなかったりする場合は、導入しない方が速い場面もありえます。
プロジェクトの性質に合わせることが大事です。

Python のリンターは種類が多いですが、結局何を入れればよいですか?

Python のエコシステムはパッケージ管理ツール(pip, pipenv, poetry, pyflow)などもそうですが、今なお多様性があり圧倒的なデファクトスタンダード(事実上の業界標準)が存在しないものが多い状況です。
多様性があることは良い部分もありますが、整理された情報が見つけにくく、他言語出身者が戸惑いやすい要因にもなりがちです。

リンターも同様で種類が多く選ぶのが大変なのですが、選定基準の一つとして「ある程度ユーザーがいて、保守され続けそうなもの」を選ぶとよいです。ここではおすすめのものを紹介します。

フォーマッター

black(整形)isort(import のソート)を紹介します。両方の導入を勧めます。

black

フォーマッターについてはデファクトスタンダードが出来上がってきたと言えると思います。
それが black です。

しかし black にも批判意見がないわけではなく、特に以下の批判が目立ちます。

  • 個別にルールを設定できません。
  • 他の有名リンターのデフォルト設定と矛盾するルールがあります。

black 開発チームはこれらの批判に対し、「PEP8 (python 公式文章の 1 つ) に準拠しているのは black です」と述べています。
筆者は black を支持しており、他を black に合わせる運用を好んでいます。

isort

isort は python のモジュール等を import する行(import requests など)をソートしてくれるツールです。
PEP8 では以下の順になるよう決められていて、isort はそれに加えて各行もアルファベット順にソートしてくれます。

  • 標準ライブラリ
  • サードパーティに関連するもの
  • ローカルな アプリケーション/ライブラリ に特有のもの

些細な部分なのでわざわざ導入するほどでもないのかもしれませんが、地味に揺れが気になったりすることがあるためか結構多くのプロジェクトで導入されている印象です。

総合的なリンター

Flake8, pylint, Prospector を紹介します。これらは重複する機能を持つので、少なくとも 1 つの導入を勧めます。

Flake8

Flake8 は pycodestyle(pep8 スタイルチェック), pyflakes(論理エラーのチェック), mccabe(複雑度チェック)をまとめたツールです。
また、プラグイン方式でルールの追加ができるのですが、プラグインの開発はあまり活発でないため期待はできません。
2021年当時は Python のリンターとして最もよく見かけるものの一つでした。

pylint

pylint は単体で pep8 スタイルチェックや論理エラーチェックなどを行なってくれるツールです。
設定ファイルによってかなり柔軟に設定を変えることができます。
pylint --generate-rcfile > .pylintrc コマンドでデフォルトの設定が生成でき、これを書き換えていくのですが、項目が多く把握が大変です。

Prospector

Prospector はさらに多くのリンターを抱き合わせることができるリンターまとめツールです。
isort も pylint も含まれており、後述の mypy や bandit もオプションで追加できます。
多くのリンターを個別に導入すると設定ファイルや実行方法が乱雑になってしまい、混乱してしまいますが、Prospector を導入すると一度にprospector .のコマンドだけで全てを実行することができるようになります。
筆者は期待しているツールですが、開発スピードはあまり速くなく、現状は依存する isort のバージョンが古いままのため black と不整合を起こしてしまうことがあります。

型チェック

mypy

mypy は型チェックを行なってくれるツールです。
Python は動的な型を持つ言語ですが、オプションとして TypeHint と呼ばれる方法で型情報を付与することができます。(参考)PEP484
Python の TypeHint の方法は Python3.9 で拡張されましたが、2021/1 の v0.800 アップデートで mypy もその拡張に対応しました。

セキュリティチェック

Bandit

Bandit はセキュリティ的に問題がありそうな箇所を判定してくれるリンターです。
他のリンターにはない観点のチェックが用意されているので、ここで紹介しました。

Python のリンターもっと紹介

VSCode にデフォルトで設定項目が存在するもの
  • black: python 公式管理のフォーマッター

  • yapf: google 製フォーマッター

  • autopep8: フォーマッター

  • pylint: リンター

  • Flake8: リンターまとめツール

  • Prospector: リンターまとめツール

  • pylama: リンターまとめツール (開発が実質止まっています)

  • Jedi: リンター+自動補完

  • Pylance: pyright を利用した Microsoft 製の VSCode 拡張の自動補完ツール

  • mypy: 型チェック

  • Bandit: セキュリティ項目のチェック

  • pycodestyle: (旧 pep8: PEP8 は文章の名前なのでツールにこの名前は紛らわしかった)リンターです。Flake8 に入っています。

  • pydocstyle: ドキュメントコメント指摘

PyCQA が管理している有名リンター

PyCQA は有名リンターを1箇所に集めて管理している GitHub Organization です。
先ほどの VSCode のリストの中では pylint,prospector,mypy,Bandit,pycodestyle,pydocstyle がこの Organization で管理されています。

  • isort: import のソートだけするフォーマッター
  • pyflakes: 論理エラーをチェックするリンターです。Flake8 に入っています。
  • mccabe: プログラムの複雑さの指標となる McCabe のチェックを行います。Flake8 に入っています。

ここまでがツールの全体像です。次は、最短で動かすための導入手順に落とします。

導入(最短ルート)

ここでは設定項目の詳細までは触れず、おすすめ構成がひとまず問題なく導入できるところまでを説明します。

紹介したツールは基本的にコマンドラインで実行することができ、設定項目をオプションとして渡して実行します。
しかし、オプションも含め長いコマンドを打つのは大変ですし、チームメンバーにどういうオプションで実行していくか伝えるのも面倒です。
そこでリポジトリ内にコマンドが記載されたスクリプトを置くことがあります。(npm 環境の場合、package.json の scripts の項目に書くようなイメージ)
これをどう実現するかは言語ごとに傾向があり、Python の場合は npm ほどデファクトスタンダードな方式があるわけではありません。ただ、OSS などを見ていると Makefile を設置する方法と tox を利用する方法をよく目にします。

Makefile はファイルにコマンドのリストを記述しておくと make lint などのように簡単に実行できて良いのですが、
tox ではさらにそのコマンドの実行環境を独立に作成してくれるため、開発環境がチームで揃っていなかったり、環境を汚したくないような場面でおすすめです。
(後述の VSCode 連携のためには、どこかしらには lint プログラムを入れる必要があります)
ということで今回は tox を用いた導入方法を紹介します。

tox

tox を利用するために tox をインストールし、tox.ini という設定ファイルを作ります。

pip install tox

ここでは今回紹介してきたリンターを実行するためのおすすめの tox.ini を紹介し、少しその意味を紹介するのに留めます。
tox は複数の Python バージョンでのテストの実行やドキュメントの作成なども行うことができるので詳しくはtox 公式ドキュメントを見てください。

tox.ini
[tox]
envlist =
    py39
    lint
    strictlint

# tox -e py39 で実行するための内容。lintではないが、一般的な tox の利用例の参考としてテストを実行する testenv を書いた。
[testenv]
deps =
    -rrequirements.txt
    pytest
commands =
    pytest -rsfp

# tox -e lint で実行するための内容。
[testenv:lint]
deps =
    black
    flake8
    isort
    mypy
commands =
    isort .
    black .
    flake8 .
    mypy .

# tox -e strictlint で実行するための内容。testenv:lint より厳しい設定で利用は必須ではない想定。
[testenv:strictlint]
ignore_errors = true
deps =
    bandit
    flake8
    mypy
commands =
    bandit --exclude ./.tox,./venv,./.venv,./**/tests --recursive .
    flake8 --ignore=W503 .
    mypy --strict .

次に、各コマンドの設定です。
ファイル数を増やしたくない場合 tox.ini に記述することができますが、setup.cfg に分離することで tox 経由以外での手動での実行にも設定を適用できるためこれを分けて設定しています。

setup.cfg
[flake8]
max-line-length = 119
exclude =
    .git
    __pycache__
    .tox
    venv
    **/migrations

[mypy]
ignore_missing_imports = True

[mypy-*.migrations.*]  # Django を利用する場合、migrations には手を加えたくないため設定。
ignore_errors = True

[isort]
profile=black


設定(迷いやすいポイント)

1 行の最大の文字数について

PEP8 では Python のプログラムの適切な 1 行あたりの文字数は 79 文字となっていますが、これは小さい画面が主流であった古い時代のルールだったとの批判があります。
black では 88 文字をデフォルトの設定としていて、筆者は black の設定に基本的に則る方針ですが、これでも(特に長い str のハードコードなどで)不便に感じることが多いです。
そこで、black が自動でフォーマットする 88 文字制限はそのまま尊重し、black がフォーマットできない長い str のハードコードなどについてはより長い 119 文字までを許容する設定にしています。
119 文字というのは GitHub のコードレビューが表示できる長さで、この文字数を支持している意見はそこそこ目にするように思います。参考)BigchainDB, stackoverflow#88942
この設定にすると、ほとんどの箇所は black の 88 文字で自動整形されつつ、手動で短くする必要がある場面では 119 文字まで許容できます。そのため、手動で整形が必要な箇所を減らせます。

運用(開発体験・自動化)

VSCode と連携する方法

おすすめの VSCode の設定を紹介します。
以下を settings.json に書き込むか、
設定を開いてpython.formatting.providerなどで検索すると出てくる項目で "black"というように選択などして設定することができます。

settings.json
{
  "python.formatting.provider": "black",
  "python.languageServer": "Pylance",
  "python.linting.flake8Args": ["--max-line-length 119"],
  "python.linting.flake8Enabled": true,
  "python.linting.mypyEnabled": true
}

リンターとはあまり関係がない項目もありますが、Python 関連では筆者はさらに以下のような設定を入れています。

settings.json
{
  "editor.rulers": [88, 119],
  "pylance.insidersChannel": "daily",
  "python.analysis.completeFunctionParens": true,
  "python.analysis.logLevel": "Warning",
  "python.analysis.memory.keepLibraryAst": true,
  "python.autoComplete.addBrackets": true,
  "python.diagnostics.sourceMapsEnabled": true,
  "python.insidersChannel": "daily",
  "python.linting.banditEnabled": true,
  "python.terminal.activateEnvInCurrentTerminal": true,
  "python.testing.pytestEnabled": true
}

強制的にチェックさせる方法

失敗しているコードを main ブランチに取り込ませないためにリンターの実行を強制する場合、実行できるタイミングは 2 つあります。

  • ローカル開発中のコミット時に、コミットしようとすると自動的にリンターが実行され、失敗するとコミットできないようにします。
  • GitHub 上で PR を投げたタイミングで、自動テスト(いわゆる CI)が(GitHub Actions などの機能を利用し)実行され、失敗するとマージできないようにします。

かなり時間のかかるテストを自動化したい場合、コミットごとに実行され待たされるのは大変なので後者を選ぶ場合が多いです。
しかし、リンター程度の数秒で終わるものであれば、前者の方が(失敗した場合にすぐ気付きすぐ対応できるという意味で)便利です。
以下では、コミット前に実行する例を紹介します。

コミット前に実行させる方法

(ここで紹介するコミット前に実行する方法に関しては OSS でも見かけることが少なく、一般的な方法とは呼べないかもしれません。npm 環境では husky を用いて行う方法が一般に知られていて多くのリポジトリでそれを見ることができるので、commit 前にリントすること自体は普及してもおかしくなさそうなのですが、python 界隈では方法の確立だけでなくそれ自体が行われていることが少ない印象です。)

git の hook 機能を用いて commit する度に lint を行わせることができます。
.git/hooks/pre-commit というファイルに行いたい動作を書けば良いのですが、.git ディレクトリは共有されないので、今回は python 製 の pre-commit というツールを利用する方法を紹介します。(git の基本機能の pre-commit と python の pre-commit が同じ名前なので紛らわしいです。)

python の pre-commit パッケージでは以下のような設定ファイルを書き、pre-commit installというコマンドを打つことで.git/hooks/pre-commitのファイルをうまく作成してくれます。

.pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: isort
        name: isort
        entry: bash -c '.tox/lint/bin/isort --check-only .'
        language: system
        types: [python]
      - id: black
        name: black
        entry: bash -c '.tox/lint/bin/black --check .'
        language: system
        types: [python]
      - id: flake8
        name: flake8
        entry: bash -c '.tox/lint/bin/flake8 .'
        language: system
        types: [python]
      - id: mypy
        name: mypy
        entry: bash -c '.tox/lint/bin/mypy .'
        pass_filenames: false
        language: system
        types: [python]

ここで、これらのリンターは個別にその場で install しながら利用することもできるのですが、今回はせっかく設定等も作りこんだ tox 環境のものをそのまま使うようにしました。
そのために事前に tox を一度起動させる必要があるので、pre-commit install と合わせてインストール用のスクリプトも作成しました。
チームメンバーには git clone 後最初の設定時に bash scripts/init-pre-commit.sh を叩いてもらうことを想定しています。

ディレクトリ構成

./
 |- tox.ini など
 |- scripts/
    |- .pre-commit-config.yaml
    |- init-pre-commit.sh
init-pre-commit.sh
PROJECT_DIR=$(cd $(dirname $0);cd ..; pwd)
python3 -m venv ${PROJECT_DIR}/.venv_precommit  # tox と pre-commit を使うためだけの一時的な環境を作る
source ${PROJECT_DIR}/.venv_precommit/bin/activate
pip install pre-commit tox
tox -c ${PROJECT_DIR}/tox.ini -e lint
pre-commit install -c ${PROJECT_DIR}/.pre-commit-config.yaml

無効化(部分的にスキップする方法)

それぞれのリンターはたくさんルールを持っていて、そのそれぞれに大抵名前がついています。(例: Flake8 の E731)
前述のようにツール自体の設定でその個別のルールを無効化することももちろんできます。
ただ、プロジェクト全体ではルールを効かせたいものの、部分的に 1 ファイルや数行だけを例外的に無効化したい場合があります。
JavaScript の eslint における // eslint-ignore-next-line のようにソースコード内のコメントでそれに対応することができます。
リンターに悩まされて開発速度が落ちては元も子もありません。積極的に無効化を利用し、そのうち知見がたまったり、余裕のあるタイミングで解消していく方針が良いように思います。

black を部分的に無効化する方法

ドキュメント)https://black.readthedocs.io/en/stable/the_black_code_style/

一部無効化する場合: 無効化したい行を # fmt: off# fmt: on で囲みます。

isort を部分的に無効化する方法

ドキュメント)https://pycqa.github.io/isort/docs/configuration/action_comments

ファイルごと無効化する場合: # isort: skip_file とファイルの先頭に書きます。
一部無効化する場合: # isort: skip, # isort: off/# isort: on, isort: split があります。詳しくはドキュメントを参照してください。

flake8 を部分的に無効化する方法

ドキュメント)https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html

ファイルごと無効化する場合: # flake8: noqa とファイルの先頭に書きます。
一部無効化する場合: # noqa とその行に書きます。(特定のルールには # noqa: E731,E123 のように書きます)

mypy を部分的に無効化する方法

ドキュメント)https://mypy.readthedocs.io/en/stable/common_issues.html

ファイルごと無効化する場合: # mypy: ignore-errors とファイルの先頭に書きます。
一部無効化する場合: # type: ignore とその行に書きます。

bandit を部分的に無効化する方法

ドキュメント)https://github.com/PyCQA/bandit#exclusions

一部無効化する場合: # nosec とその行に書きます。

まとめ

  • おすすめは black + isort + flake8 + mypytox でまとめて回す構成です。
  • まずは tox -e lint をローカル/CI の共通コマンドにして「1コマンドで lint」を作ります。
  • 詰まりやすいところ(行長、VSCode 連携、強制実行)は目的別に整えると続きます。
  • どうしても通らない箇所は、コメントによる一時的な無効化をチームで共有しておくと安心です。

実務メモ(落とし穴・代替案・検証)

落とし穴

  • black と他ツール(例: flake8 の行長など)のデフォルトが噛み合わず詰まりやすいです(設定を揃えます)
  • mypy をいきなり strict にすると止まりやすいです(段階的に上げます)
  • コミット時に強制すると詰まりやすいです(チームの合意と対象範囲を絞ります)
  • 無効化コメントが溜まると負債になります(理由を残し、範囲を最小にします)

代替案と比較軸

  • flake8: 広く使われていて、プラグインで拡張しやすいです
  • pylint: ルールが多く厳しめにできます(設定コストは高めです)
  • Prospector: まとめて回せますが、依存の追随状況は確認が必要です
  • 比較軸: 欲しい検査(整形/バグ/型/セキュリティ)、速度、設定コスト、チーム運用

判断フロー(最短)

  • まず整形を揃えます: black + isort
  • バグの可能性があるパターンを拾います: flake8/pylint のどちらか
  • 型が必要になったら: mypy を足します(段階的)
  • セキュリティ観点が必要なら: bandit を追加します

検証(最小)

  • tox -e lint がローカル/CI で同じ結果になります
  • 自動整形(black/isort)は差分が収束します(繰り返し直りません)

セキュリティ(最低限)

  • bandit は静的解析なので過信せず、レビューと運用(設定/権限/鍵管理)もセットで考えます

参考(一次情報)

更新: 2025-12-29(冒頭に対象/結論と最短導入を追記、見出しを段落中心に整理、参考(一次情報)を追加、落とし穴、代替案、判断フロー、検証観点、注意事項を追記)

Discussion

murata100murata100

settings.json ですが、python.linting.flake8Args は以下のように書かないと --max-line-length 119 というファイルがないというエラーになりました。

  "python.linting.flake8Args": ["--max-line-length", "119"],