Python開発を劇的に改善!pre-commit徹底活用ガイド 🚀

開発ツール

コミット前の自動チェックでコード品質を高め、開発効率を向上させよう!

Pythonでの開発プロジェクト、順調に進んでいますか?コードを書くのは楽しいけれど、コードレビューでの細かい指摘や、チーム内でのコーディングスタイルのばらつきに悩まされることも少なくありませんよね😥。特に、末尾の不要な空白、importの順序、フォーマットの乱れなどは、本質的なロジックとは関係ない部分で時間を取られてしまいがちです。

そんな悩みを解決してくれる強力なツールが pre-commit です! pre-commitは、Gitのコミット前に自動的にコードチェックやフォーマットを実行してくれるフレームワークです。これを使えば、コミット前に潜在的な問題を検出し、修正を促すことで、コード品質を一貫して高く保つことができます。

この記事では、pre-commitの基本的な概念から、導入方法、便利なフックの設定、そしてチームでの効果的な運用方法まで、詳しく解説していきます。さあ、pre-commitをマスターして、より快適で生産的なPython開発ライフを送りましょう!✨

pre-commitは、Gitのコミットフック(特定のGitアクションの前後に自動実行されるスクリプト)の中でも、pre-commitというフックを簡単に管理・共有できるようにするためのフレームワークです。Pythonで書かれていますが、Pythonプロジェクトだけでなく、様々な言語のプロジェクトで利用できます。

Gitには元々フックスクリプト機能がありますが、.git/hooks/ディレクトリ以下に置かれるため、そのままではGitリポジトリでバージョン管理できず、チームメンバー間で共有するのが面倒でした。pre-commitはこの問題を解決し、設定ファイル(.pre-commit-config.yaml)をリポジトリに含めることで、誰でも同じチェックを簡単に実行できるようにします。

pre-commitを使うメリット

  • コード品質の向上: コミット前に自動でLint(静的解析)やフォーマットを実行し、潜在的なバグやスタイル違反を早期に発見・修正できます。
  • 一貫性の確保: チーム全員が同じルールでコードチェックを行うため、コーディングスタイルや規約を統一しやすくなります。
  • レビュー効率の向上: レビュアーは、些細なスタイル修正ではなく、ロジックや設計といった本質的な部分に集中できます。
  • 自動化による手間削減: 手動でLinterやフォーマッターを実行する手間が省け、開発者はコーディングに集中できます。
  • 導入の容易さ: 豊富な既存フックが利用可能で、設定ファイルを書くだけで簡単に導入できます。

これらのメリットにより、開発プロセス全体の効率化とコード品質の向上が期待できます。特にチーム開発においては、コードの一貫性を保つために非常に有効なツールです。

pre-commitの導入は非常に簡単です。以下の手順でセットアップを進めましょう。

2.1. インストール

pipを使ってpre-commitをインストールします。開発時にのみ使用するため、--devオプション(Poetryの場合)や-Dオプション(pipenvの場合)、または開発用の依存関係グループに含めるのが一般的です。

# pip を使う場合
pip install pre-commit

# Poetry を使う場合
poetry add --dev pre-commit

# pipenv を使う場合
pipenv install --dev pre-commit

# Homebrew (macOS) を使う場合
brew install pre-commit

# Conda を使う場合
conda install -c conda-forge pre-commit

インストール後、バージョンが表示されるか確認しましょう。

pre-commit --version

2.2. 設定ファイルの作成

プロジェクトのルートディレクトリに、.pre-commit-config.yamlという名前の設定ファイルを作成します。このファイルに、実行したいフック(チェックツール)とその設定を記述します。

まずは、サンプル設定ファイルを生成してみましょう。

pre-commit sample-config > .pre-commit-config.yaml

生成された.pre-commit-config.yamlは以下のようになります(バージョンは異なる場合があります)。

# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0 # 例: 最新の安定版タグを指定
    hooks:
    -   id: trailing-whitespace # 末尾の空白をチェック・削除
    -   id: end-of-file-fixer # ファイル末尾が単一の改行であることを保証
    -   id: check-yaml # YAMLファイルの構文をチェック
    -   id: check-added-large-files # 追加されたファイルが大きすぎないかチェック

この設定ファイルについては、次のセクションで詳しく解説します。

2.3. Gitフックのインストール

設定ファイルを作成したら、以下のコマンドを実行して、pre-commitフックをGitリポジトリにインストールします。

pre-commit install

成功すると、pre-commit installed at .git/hooks/pre-commitのようなメッセージが表示されます。これにより、git commitコマンドを実行するたびに、.pre-commit-config.yamlで設定されたフックが自動的に実行されるようになります。

⚠️ 注意: pre-commit install は、リポジトリをクローンするたびに各開発者が実行する必要があります。チームメンバーに周知しましょう。

これで、pre-commitの基本的なセットアップは完了です!🎉 次は設定ファイルの詳細を見ていきましょう。

.pre-commit-config.yaml は、pre-commitの動作を制御する心臓部です。YAML形式で記述され、主にreposキーの下に、使用するフックとその設定をリスト形式で定義します。

3.1. 基本構造

repos:
-   repo: <リポジトリURL or local> # フックを提供するリポジトリの場所
    rev: <バージョン or タグ or コミットハッシュ> # 使用するリポジトリのバージョン
    hooks:
    -   id: <フックID> # 実行するフックの一意な識別子
        # --- 以下、オプション設定 ---
        name: <表示名> # フックのカスタム名 (ログ出力用)
        entry: <実行コマンド> # デフォルト以外のコマンドを実行する場合
        language: <言語> # フックの実行に必要な言語 (python, node, ruby, etc.)
        files: <正規表現> # 対象とするファイルを限定
        exclude: <正規表現> # 対象から除外するファイル
        types: [<ファイルタイプ>, ...] # 対象とするファイルタイプ (python, text, json, etc.)
        args: [<引数1>, <引数2>, ...] # フック実行時に渡す引数
        stages: [<ステージ>, ...] # フックを実行するGitステージ (commit, push, manual, etc.)
        additional_dependencies: [<依存パッケージ>, ...] # フック実行に必要な追加ライブラリ
        always_run: <true/false> # ファイル変更がなくても常に実行するかどうか
        verbose: <true/false> # フックの出力を詳細にするか
        # ... その他のオプション ...

# --- グローバル設定 (オプション) ---
# default_stages: [commit, push] # デフォルトでフックを実行するステージ
# default_language_version:
#     python: python3.10 # Pythonフックのデフォルトバージョン指定
# files: <正規表現> # 全フックのデフォルト対象ファイル
# exclude: <正規表現> # 全フックのデフォルト除外ファイル
# fail_fast: true # いずれかのフックが失敗したら即座に中止する
# minimum_pre_commit_version: '2.9.0' # 必要なpre-commitの最小バージョン

3.2. 主要なキーの説明

キー説明
repoフックを提供するリポジトリのURL、またはローカルフックの場合は local を指定します。https://github.com/psf/black
local
rev使用するリポジトリのバージョンを指定します。タグ名、コミットハッシュ、ブランチ名などが使えます。安定した運用のため、特定のタグを指定することが推奨されます。'24.4.0'
v1.2.3
'a1b2c3d'
hooksこのリポジトリから使用するフックのリストを定義します。各要素は辞書形式で、idが必須です。- id: black
- id: flake8
id実行したいフックの一意なID。通常、リポジトリ側で定義されています。trailing-whitespace
argsフック実行時に渡すコマンドライン引数をリストで指定します。args: ["--max-line-length=88", "--ignore=E203,W503"]
filesフックの対象とするファイルをPythonの正規表現で指定します。指定しない場合は、リポジトリのデフォルト設定またはファイルタイプに基づいて判断されます。files: \.py$ (Pythonファイルのみ対象)
excludeフックの対象から除外するファイルをPythonの正規表現で指定します。exclude: ^(docs/|tests/) (docs/ と tests/ ディレクトリ以下を除外)
types対象とするファイルのタイプを指定します。pre-commitはファイルの内容や拡張子からタイプを推測します (例: python, text, yaml, json)。types: [python]
stagesフックを実行するGitのステージを指定します。デフォルトはcommitステージ(git commit時)です。他にpush, commit-msg, post-commit, post-checkout, manualなどがあります。stages: [commit, push] (コミット時とプッシュ時に実行)
languageフックの実行に必要な言語を指定します (例: python, ruby, node, rust, golang, system)。systemを指定すると、システムにインストール済みのツールを使用します(pre-commitによる環境管理外)。language: python
additional_dependenciesフックの実行に必要な追加のライブラリを指定します (主にlanguage: pythonの場合)。additional_dependencies: ['flake8-bugbear']

これらのキーを組み合わせることで、プロジェクトの要件に合わせて柔軟にフックを設定できます。次のセクションでは、Python開発でよく使われる便利なフックを紹介します。

pre-commitには、多くの便利なフックが公開されており、これらを組み合わせることでコード品質を効率的に管理できます。ここでは、特にPythonプロジェクトでよく利用される代表的なフックを紹介します。

フック (ID)リポジトリ概要主な役割
trailing-whitespacepre-commit/pre-commit-hooks行末の不要な空白を削除します。スタイル統一
end-of-file-fixerファイルが単一の改行で終わるように修正します。スタイル統一
check-yamlYAMLファイルの構文をチェックします。設定ファイル検証
check-jsonJSONファイルの構文をチェックします。設定ファイル検証
check-tomlTOMLファイルの構文をチェックします。設定ファイル検証
check-added-large-filesGitに追加されるファイルサイズが大きい場合に警告します(デフォルト500KB)。args: ['--maxkb=1024']などで閾値を変更可能。リポジトリ管理
blackpsf/black妥協しないPythonコードフォーマッター。自動でコードスタイルを統一します。コードフォーマット
ruffastral-sh/ruff-pre-commit非常に高速なPythonリンター兼フォーマッター。Flake8やisortなどの機能を統合し、高速に動作します。最近人気が高まっています。Lint, コードフォーマット
flake8PyCQA/flake8Pythonの静的解析ツール(リンター)。PEP 8スタイルガイド違反や論理エラーの可能性などを検出します。argsでルール調整可能。Lint (静的解析)
isortPyCQA/isortPythonのimport文を自動でソート・整形します。blackと互換性のあるプロファイル設定が推奨されます (args: ["--profile", "black"])。コードフォーマット
mypypre-commit/mirrors-mypyPythonの静的型チェッカー。型ヒントに基づいてコードの型安全性を検証します。args: ["--ignore-missing-imports"]などがよく使われます。静的型チェック
pyupgradeasottile/pyupgrade古いPython構文を新しいバージョンへ自動でアップグレードします。args: ['--py38-plus']のように対象バージョンを指定。コード近代化
banditPyCQA/banditPythonコードの一般的なセキュリティ問題を検出するツール。セキュリティチェック

設定例: Ruff, Black, Mypy を使う場合

最近人気の高いRuffを中心に、Black(フォーマットの最終確認)とMypy(型チェック)を組み合わせた設定例です。

repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
    -   id: trailing-whitespace
    -   id: end-of-file-fixer
    -   id: check-yaml
    -   id: check-added-large-files

-   repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.4.4 # Ruffのバージョンを指定
    hooks:
    -   id: ruff
        args: [--fix, --exit-non-zero-on-fix] # 自動修正を有効にし、修正があった場合にエラーとする
    -   id: ruff-format

# Ruffのフォーマット後に念のためBlackも実行 (オプション)
# -   repo: https://github.com/psf/black
#     rev: 24.4.2
#     hooks:
#     -   id: black

-   repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.10.0 # Mypyのバージョンを指定
    hooks:
    -   id: mypy
        args: [--ignore-missing-imports, --strict] # 必要に応じて引数を調整
        additional_dependencies: [types-requests] # 型定義が必要なライブラリを追加

💡 ヒント: Ruffは非常に多機能で、Flake8やisortなどの機能を内包しています。Ruffを導入する場合、これらのツールは不要になることが多いです。設定はプロジェクトのpyproject.tomlファイル内の[tool.ruff]セクションで行うのが一般的です。

これらのフックを適切に設定することで、コーディングの多くの側面を自動でチェック・修正できるようになります。プロジェクトの特性に合わせて、必要なフックを選択・設定しましょう。

pre-commitをセットアップしたら、日常の開発フローの中でどのように動作し、どう運用していくかを見ていきましょう。

5.1. コミット時の自動実行

pre-commit install を実行した後、git commit を実行すると、ステージングされた(git addされた)ファイルに対して、.pre-commit-config.yaml で設定されたフックが自動的に実行されます。

  • フックがすべて成功した場合: コミットメッセージの入力に進み、通常通りコミットが完了します。
  • いずれかのフックが失敗した場合: エラーメッセージが表示され、コミットは中断されます。多くのフォーマッターフック(例: black, ruff –fix)は、問題を自動修正します。自動修正された場合は、再度 git add で変更をステージングし、もう一度 git commit を実行する必要があります。
# 1回目のコミット (フックが失敗し、自動修正される)
$ git commit -m "Implement feature X"
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Ruff.....................................................................Failed
- hook id: ruff
- files were modified by this hook

reformatting file.py
Found 5 errors (3 fixed, 2 remaining)
Mypy.....................................................................Passed

# 自動修正されたファイルをステージング
$ git add file.py

# 2回目のコミット (今度は成功するはず)
$ git commit -m "Implement feature X"
Trim Trailing Whitespace.................................................Passed
Fix End of Files.........................................................Passed
Check Yaml...............................................................Passed
Ruff.....................................................................Passed
Mypy.....................................................................Passed
[main a1b2c3d] Implement feature X
 1 file changed, 5 insertions(+), 3 deletions(-)

5.2. 手動実行

コミット時以外にも、手動でフックを実行できます。

  • ステージングされたファイルに対して実行:
    pre-commit run
  • すべての管理ファイルに対して実行:
    pre-commit run --all-files

    CI/CDパイプラインでプロジェクト全体のチェックを行う場合などに便利です。

  • 特定のファイルに対して実行:
    pre-commit run --files path/to/file1.py path/to/file2.py
  • 特定のフックのみ実行:
    pre-commit run <hook_id>

    例: pre-commit run black

  • 特定のフックのみ全ファイルに対して実行:
    pre-commit run <hook_id> --all-files

    例: pre-commit run ruff --all-files

5.3. フックの更新

使用しているフックのバージョンを最新に保つことも重要です。pre-commit autoupdateコマンドを使うと、.pre-commit-config.yaml内のrevを各リポジトリの最新のタグ(通常は安定版)に自動で更新できます。

pre-commit autoupdate

更新後、.pre-commit-config.yamlの変更をコミットすることを忘れないでください。定期的に(例えば、月に一度など)実行することが推奨されます。

💡 ヒント: Poetryなど一部ツールのフックは、autoupdateが意図通りに動作しない場合があります。その場合は手動で更新するか、pre-commit autoupdate --repo <repo_url> で更新対象リポジトリを明示的に指定する、または pre-commit-update のような代替ツールを検討してください。

5.4. CI/CDへの組み込み

ローカルでのチェックに加え、CI/CDパイプライン(例: GitHub Actions, GitLab CI, Jenkins)でもpre-commitを実行することで、チェック漏れを防ぎ、コード品質をさらに確実に保証できます。

CI環境では、通常pre-commit run --all-filesを実行して、リポジトリ全体のファイルをチェックします。

GitHub Actionsでの設定例:

# .github/workflows/pre-commit.yml
name: Pre-Commit Checks

on: [pull_request]

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with:
        python-version: '3.10' # プロジェクトで使用するPythonバージョン
    - name: Install pre-commit and dependencies
      run: pip install pre-commit && pre-commit install-hooks # 必要に応じて依存関係もインストール
    - name: Run pre-commit checks
      run: pre-commit run --all-files

これにより、Pull Requestが作成されるたびに自動でチェックが実行され、問題があればCIが失敗するため、マージ前に問題を検知できます。

公開されているフックだけでは満たせない、プロジェクト固有のチェックや処理を実行したい場合もあります。そんな時は、自分でスクリプトを作成し、それをpre-commitフックとして実行するローカルフックを作成できます。

ローカルフックを定義するには、.pre-commit-config.yamlrepo: localを指定します。

設定例: プロジェクト固有のテストスクリプトを実行

プロジェクトルートにscripts/run_custom_tests.shというカスタムテストスクリプトがあるとします。これをコミット前に実行するローカルフックの設定は以下のようになります。

scripts/run_custom_tests.sh (例):

#!/bin/bash
set -e # エラーが発生したら即座に終了

echo "Running custom project tests..."
# ここにカスタムテストのコマンドを記述
# 例: python manage.py custom_check
# 例: npm run lint:custom

# テストが失敗したら non-zero で終了させる
# if [ test_failed ]; then
#  echo "Custom tests failed!"
#  exit 1
# fi

echo "Custom tests passed!"
exit 0

⚠️ 注意: スクリプトファイルには実行権限が必要です (chmod +x scripts/run_custom_tests.sh)。

.pre-commit-config.yaml への追記:

repos:
# ... 他のフック設定 ...

-   repo: local
    hooks:
    -   id: custom-tests
        name: Run custom project tests
        entry: scripts/run_custom_tests.sh
        language: script # または system (シェルスクリプトの場合)
        # stages: [commit] # デフォルトはcommitステージ
        # files: \.py$ # 必要であれば対象ファイルを限定
        pass_filenames: false # スクリプトにファイル名を渡さない場合はfalse (デフォルトはtrue)
        always_run: true # ファイル変更がなくても常に実行したい場合

ローカルフックのポイント

  • id: フックの一意な名前を自分で決めます。
  • name: ログ出力時に表示される分かりやすい名前を指定します。
  • entry: 実行するスクリプトのパスまたはコマンドを指定します。
  • language: スクリプトを実行する環境を指定します。
    • script: シェルスクリプトなど、実行権限のあるファイルを実行する場合。
    • system: システムにインストールされているコマンドを実行する場合 (例: entry: mypy)。依存関係は自分で管理する必要があります。
    • python: Pythonスクリプトを実行する場合。pre-commitが仮想環境を管理します。additional_dependenciesで必要なライブラリを指定できます。
    • その他、node, ruby なども指定可能です。
  • pass_filenames: true (デフォルト) の場合、フックの対象となったファイル名がコマンドライン引数としてスクリプトに渡されます。不要な場合はfalseにします。
  • always_run: trueにすると、対象ファイルの変更有無に関わらず、常にフックが実行されます。テスト実行などに向いています。

ローカルフックを活用することで、pre-commitの適用範囲をさらに広げ、プロジェクト固有の要件にも柔軟に対応できます。

pre-commitには、さらに細かい制御や便利な機能があります。いくつか紹介しましょう。

7.1. ステージ (Stages) の活用

デフォルトではフックはcommitステージ(git commit時)に実行されますが、stagesキーを使って他のステージを指定できます。

  • commit: (デフォルト) コミット作成前に実行。
  • commit-msg: コミットメッセージ編集後に実行。コミットメッセージのフォーマットチェックなどに使えます。
  • post-commit: コミット完了後に実行。通知などに使えます。
  • pre-push: git push前に実行。より時間のかかるテスト(例: 統合テスト)や、リモートにプッシュする前の最終チェックに適しています。
  • post-checkout: git checkoutgit switch後に実行。
  • manual: 手動実行(pre-commit run --hook-stage manual)でのみ実行。CIでのみ実行したい重い処理などに。

設定例: プッシュ前にテストを実行

-   repo: local
    hooks:
    -   id: pytest-check
        name: Run pytest
        entry: pytest -v tests/
        language: system # or python with additional_dependencies: [pytest]
        stages: [push] # プッシュ前に実行
        pass_filenames: false
        always_run: true

ステージを使い分けることで、チェックのタイミングを最適化できます。

7.2. フックのスキップ

一時的に特定のフック、あるいはすべてのフックをスキップしてコミットしたい場合があります。

  • すべてのフックをスキップ: --no-verify (または -n) オプションを使います。注意: この方法はチェックを完全に迂回するため、緊急時以外は推奨されません。
    git commit -m "Hotfix, skip hooks" --no-verify
  • 特定のフックのみスキップ: 環境変数 SKIP を使います。スキップしたいフックのIDをカンマ区切りで指定します。
    SKIP=flake8,mypy git commit -m "Work in progress, skip linters"

7.3. 環境変数

pre-commitはいくつかの環境変数を認識します。例えば、PRE_COMMIT_HOMEでキャッシュディレクトリの場所を変更できます。

7.4. 複数設定ファイルの利用

--config (または -c) オプションで、デフォルトの.pre-commit-config.yaml以外の設定ファイルを指定して実行できます。

pre-commit run --config .pre-commit-config-ci.yaml --all-files

7.5. fail_fast オプション

.pre-commit-config.yamlのトップレベルでfail_fast: trueを設定すると、いずれかのフックが失敗した時点で、後続のフックを実行せずに即座にpre-commitプロセス全体を停止します。CI環境などで実行時間を短縮したい場合に有効です。

fail_fast: true
repos:
  # ...

これらの高度な機能を活用することで、pre-commitをよりプロジェクトのニーズに合わせてカスタマイズできます。

pre-commitは、Gitのコミットプロセスに自動チェックを組み込むことで、Pythonプロジェクト(もちろん他の言語でも!)のコード品質と一貫性を劇的に向上させる強力なツールです。

この記事では、以下の点について解説しました:

  • ✅ pre-commitの基本的な概念とそのメリット
  • ✅ 簡単なインストールとセットアップ方法
  • .pre-commit-config.yaml 設定ファイルの詳細な書き方
  • ✅ Python開発で役立つ代表的なフック (Ruff, Black, Flake8, isort, Mypy など)
  • ✅ コミット時の自動実行、手動実行、フックの更新方法
  • ✅ CI/CDパイプラインへの組み込み
  • ✅ プロジェクト固有のチェックを実現するローカルフックの作成方法
  • ✅ ステージの活用やフックのスキップなどの高度なTips

pre-commitを導入することで、開発チームは以下のような恩恵を受けられます:

  • ✨ コードレビューの負担軽減(些細な指摘が減る)
  • ✨ コードベース全体の一貫性向上
  • ✨ バグや潜在的な問題の早期発見
  • ✨ 開発プロセスの効率化
  • ✨ 新メンバーのオンボーディング支援(コーディング規約の強制)

まだpre-commitを導入していない方は、ぜひこの機会に試してみてはいかがでしょうか?設定は簡単で、得られる効果は絶大です。自動化の力を借りて、より質の高いコードを、より効率的に開発しましょう! 💪

Happy coding! 😄

コメント

タイトルとURLをコピーしました