Python開発: コード品質を一歩前進させるpre-commit導入のすゝめ

# はじめに
Pythonの開発現場でコード品質を保つために、**一貫したコードスタイル**や**簡単なコードチェック**を自動化することは欠かせません。
`pre-commit`は、その自動化を強力にサポートしてくれるツールです。このツールを使えば、コミット前にコードをチェックし、自動でフォーマットやスタイルを統一できます。

今回はpre-commitを導入して、Pythonコードの品質管理を行う手順について解説します。
併せて、コード整形ツール`black`や`isort`、静的解析ツール`flake8`を使った一連の設定手順も紹介します。

:::note note
pre-commitを導入している例として有名なWebフレームワーク[**Django**](https://github.com/django/django)もpre-commitを導入しているようです。
:::

# pre-commitの導入手順
## 1. インストール
まずはPythonの環境を使用し`pre-commit`をインストールします。
```bash
pip install pre-commit
```

## 2. `.pre-commit-config.yaml`をリポジトリ直下に作成
`pre-commit`は、設定ファイル`.pre-commit-config.yaml`を通じて使用するツールやその設定を管理します。
以下の例では、コード整形ツール`black`、`isort`、静的解析ツール`flake8`を設定しています。
```yaml:.pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 24.10.0
    hooks:
      - id: black
  - repo: https://github.com/pycqa/flake8
    rev: 7.1.1
    hooks:
      - id: flake8
        args:
          - "--max-line-length=88"
          - "--ignore=E203,W503"
  - repo: https://github.com/pycqa/isort
    rev: 5.13.2
    hooks:
      - id: isort
```

## 3. pre-commit
設定ファイルを作成したら、`pre-commit`をリポジトリにインストールします。
```bash
pre-commit install
```

成功すると以下のようなメッセージが表示されます。
```
pre-commit installed at .git/hooks/pre-commit
```


## 実際にpre-commitを実行してみる

今回pre-commitの動作確認として以下のコードを作成してみました。
pre-commitを実際に実行するには`pre-commit install`を完了後、対象ファイルをGitのコミットするだけで実行してくれます。
コミットではなくコマンドから実行する場合は`pre-commit run --files ファイル名`で実行可能です。
### 修正が必要なコードの例
以下のコードにはいくつかの問題点があります
- importが標準ライブラリと外部ライブラリ、自作モジュールで分かれていない
- importと実装コードとの改行がない
- シングルクォート(')とダブルクォート(")の統一されていない
- 使用していない不要なimport文がある
- 使用していない変数がある
```python
import os  # 標準ライブラリ
from my_module.module import SampleClass  # 自作モジュール
from my_util.util import sample_method  # 自作モジュール
from playwright.async_api import async_playwright  # サードパーティ
import re  # 標準ライブラリ
def sample(id:int, last_name: str,first_name: str,email: str, password: str, age: int, friends: list) -> None:
    sample_method()
    sample_data = {"sample_a": "test_a","sample_b": "test_b","sample_c": "test_c",
        'sample_d': 'test_d',
        'sample_e': 'test_e',
        'sample_f': 'test_f',
        'sample_g': 'test_g',
    }
    print("サンプルコード")
```

### pre-commit適用後の結果
`pre-commit`実行後、コードは次のように修正されます

```python
import os  # 標準ライブラリ
import re  # 標準ライブラリ

from playwright.async_api import async_playwright  # サードパーティ

from my_module.module import SampleClass  # 自作モジュール
from my_util.util import sample_method  # 自作モジュール


def sample(
    id: int,
    last_name: str,
    first_name: str,
    email: str,
    password: str,
    age: int,
    friends: list,
) -> None:
    sample_method()

    sample_data = {
        "sample_a": "test_a",
        "sample_b": "test_b",
        "sample_c": "test_c",
        "sample_d": "test_d",
        "sample_e": "test_e",
        "sample_f": "test_f",
        "sample_g": "test_g",
    }

    print("サンプルコード")

```
isortでimport文の順番が整頓され、blackでコードが綺麗に整形してくれます。

flake8では使用していないimport文や使用されていない変数などのエラー箇所を指摘します。
これにより、コミット時に間違ったコードが混入するリスクを軽減できます。

```terminal
(venv) MacBook-Air pre-commit-sample % git commit -m "pre-commit test"
black....................................................................Failed
- hook id: black
- files were modified by this hook

reformatted sample.py

All done! ✨ 🍰 ✨
1 file reformatted.

flake8...................................................................Failed
- hook id: flake8
- exit code: 1

sample.py:1:1: F401 'os' imported but unused
sample.py:2:1: F401 'my_module.module.SampleClass' imported but unused
sample.py:4:1: F401 'playwright.async_api.async_playwright' imported but unused
sample.py:5:1: F401 're' imported but unused
sample.py:19:5: F841 local variable 'sample_data' is assigned to but never used

isort....................................................................Failed
- hook id: isort
- files were modified by this hook

Fixing /Users/name/Desktop/Qiita記事/pre-commit-sample/sample.py
```

## pre-commitでチェックをスキップしたい箇所がある場合
時々pre-commitのチェックを避けたい箇所があります。
その時は`# noqa: エラーコード`のコメントを入れるとチェックを避けることができます。
以下は使用していない不要なimportがあるときの`F401`のエラーコードのチェックをスキップするケースです。
```python
import os  # noqa: F401
```

# まとめ
pre-commitを活用することでコードの一貫性が向上しチーム全体で同じスタイルを維持できたり、レビューやバグ修正のコストを削減できることでしょう。


Pythonプロジェクトで手軽に品質管理を始めたい方は、ぜひpre-commitを導入してみてください！