コンテンツにスキップ

2.2.6 型注釈とコード品質

型注釈とコード品質のフローチャート

このページを終えたら、この evidence card を残します。

パターン
class、exception、file IO、functional pipeline、generator、またはtype hint
コード成果物
最小限の実行可能な例と、現実的なユースケース 1 つ
出力
印字されたオブジェクト状態、捕捉したエラー、保存したファイル、yieldされた値、または型チェックのメモ
失敗確認
隠れた変更、副作用を飲み込む例外、ファイルパスの問題、lazy iterator の混同、または誤解を招く注釈
期待される成果
デバッグメモを含む小さな高度Python例

この節では、「コードが動く」から一歩進んで、「コードを保守しやすくする」ことに注目します。型注釈、フォーマッタ、コードチェックツールを使うと、プロジェクトが大きくなったときや、複数人で協力するとき、複雑なライブラリを使うときにミスを減らせます。また、未来の自分もコードをすばやく理解しやすくなります。

  • なぜ型注釈が必要なのかを理解する
  • Python の型注釈の基本構文を身につける
  • よく使うコード品質ツール(linter、formatter)を知る
  • 高品質なコードを書く習慣をつける

Python は動的型付け言語です。変数を宣言するときに型を書く必要はありません。これはとても柔軟ですが、一方で次のような問題もあります。

def calculate_total(items, tax):
return sum(items) * (1 + tax)
# 使うときには、推測する必要がある:
# items は何? リスト? タプル?
# tax は何? 0.1? それとも "10%"?
# 戻り値は何? 数字? 文字列?

プロジェクトが大きくなると、型情報のないコードは道標のない高速道路のようなものです。頼れるのは勘だけになってしまいます。

型注釈の役割は次のとおりです。

メリット説明
自己文書化関数が何を受け取り、何を返すかが一目でわかる
IDE のスマート補完VS Code でより正確な自動補完が使える
静的チェック実行前に型の間違いを見つけられる
チーム開発コミュニケーションコストを下げ、コード自体が意図を伝えてくれる

# 基本型
feature_name: str = "ログイン API"
retry_count: int = 3
latency_ms: float = 125.5
is_enabled: bool = True
# Python は型注釈を強制しない
# 次のコードはエラーにならないが、静的チェックツールは警告する
retry_count: int = "三回" # 型注釈では int だが、実際には str を代入している
def greet(name: str) -> str:
"""
name: str → 引数 name の型は str
-> str → 戻り値の型は str
"""
return f"こんにちは、{name}!"
def calculate_bmi(weight: float, height: float) -> float:
"""BMI を計算する"""
return weight / (height ** 2)
def train_model(epochs: int = 10, lr: float = 0.001) -> None:
"""None を返す関数"""
print(f"{epochs} エポックを学習します。学習率は {lr} です")

型注釈があると、VS Code のスマート補完がとても正確になります。greet( と入力したときに、引数の型が str だとすぐにわかります。


# Python 3.9+:組み込み型をそのまま使える
estimated_hours: list[int] = [8, 12, 5]
task_hours: dict[str, int] = {"ログイン API": 8, "RAG デモ": 12}
coordinates: tuple[float, float] = (3.14, 2.71)
unique_ids: set[int] = {1, 2, 3}
# Python 3.8 以前:typing から import する必要がある
from typing import List, Dict, Tuple, Set
estimated_hours: List[int] = [8, 12, 5]
task_hours: Dict[str, int] = {"ログイン API": 8, "RAG デモ": 12}

Optional: None になる可能性がある値

Section titled “Optional: None になる可能性がある値”
from typing import Optional
def find_task(name: str) -> Optional[dict]:
"""タスクを探す。見つからなければ None を返す"""
tasks = {"ログイン API": {"hours": 8}, "RAG デモ": {"hours": 12}}
return tasks.get(name)
# Python 3.10+ では、より簡潔に書ける
def find_task(name: str) -> dict | None:
tasks = {"ログイン API": {"hours": 8}, "RAG デモ": {"hours": 12}}
return tasks.get(name)
from typing import Union
def process(data: Union[str, list]) -> str:
"""文字列またはリストを受け取る"""
if isinstance(data, list):
return ", ".join(str(item) for item in data)
return data
# Python 3.10+ の簡潔な書き方
def process(data: str | list) -> str:
if isinstance(data, list):
return ", ".join(str(item) for item in data)
return data
from typing import Callable
def apply_func(func: Callable[[int, int], int], a: int, b: int) -> int:
"""関数を引数として受け取る"""
return func(a, b)
result = apply_func(lambda x, y: x + y, 3, 5) # 8
from typing import Any, Literal
# Any:任意の型
def log(message: Any) -> None:
print(message)
# Literal:特定の値だけを受け取る
def set_mode(mode: Literal["train", "eval", "test"]) -> None:
print(f"モード: {mode}")
set_mode("train") # ✅
set_mode("play") # 静的チェックで警告される

def analyze_latencies(
latencies: list[float],
endpoint: str = "不明",
slow_line: float = 800.0
) -> dict[str, float | int | str]:
"""API レイテンシを分析し、統計情報を返す"""
if not latencies:
return {"error": "レイテンシリストが空です"}
return {
"endpoint": endpoint,
"count": len(latencies),
"average": sum(latencies) / len(latencies),
"max": max(latencies),
"min": min(latencies),
"slow_count": sum(1 for ms in latencies if ms >= slow_line)
}
class DataProcessor:
def __init__(self, name: str, data: list[dict[str, Any]]) -> None:
self.name: str = name
self.data: list[dict[str, Any]] = data
self._processed: bool = False
def filter_by(self, key: str, value: Any) -> list[dict[str, Any]]:
"""条件に応じてデータをフィルタする"""
return [item for item in self.data if item.get(key) == value]
def get_column(self, key: str) -> list[Any]:
"""ある列を取り出す"""
return [item[key] for item in self.data if key in item]

良いコードは、動くだけではなく、読みやすく、ルールがそろっていて、バグが少ないことも大切です。ここでは、そのためのツールを紹介します。

black は Python で最もよく使われるコードフォーマッタです。コードを自動で統一されたスタイルに整えてくれます。

Terminal window
# インストール
pip install black
# 1ファイルを整形
black my_script.py
# ディレクトリ全体を整形
black src/
# 変更せずにチェックだけする
black --check my_script.py

整形前:

x = { 'a':37,'b':42,
'c':927}
y = 'hello ''world'
z = 'hello '+'world'
a = [1,2,3,4,5,]

整形後:

x = {"a": 37, "b": 42, "c": 927}
y = "hello " "world"
z = "hello " + "world"
a = [1, 2, 3, 4, 5]

ruff は新世代の Python linter で、とても高速です。よくある問題をたくさん見つけてくれます。

Terminal window
# インストール
pip install ruff
# コードをチェック
ruff check my_script.py
# 自動修正
ruff check --fix my_script.py
# 整形(ruff は black の代わりにも使える)
ruff format my_script.py
Terminal window
# インストール
pip install mypy
# 型をチェック
mypy my_script.py
example.py
def add(a: int, b: int) -> int:
return a + b
result = add("hello", "world") # mypy がエラーを出す: 引数の型が違う!
Terminal window
$ mypy example.py
example.py:4: error: Argument 1 to "add" has incompatible type "str"; expected "int"

VS Code で次の拡張機能を入れると、コード品質の問題をリアルタイムで確認できます。

拡張機能機能
Pylance型チェックとスマート補完(VS Code の標準おすすめ)
Ruffリアルタイムのコードチェック、必要ならフォーマットも担当
Black Formatterチームが Black を標準フォーマッタにしている場合の保存時フォーマット

新しいプロジェクトでは、Ruff に lint と format の両方を任せるとツールチェーンがシンプルになります。VS Code の設定に以下を追加します。

{
"editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff",
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff"
}
}

チームですでに Black を標準にしている場合は、Black Formatter をデフォルトフォーマッタにし、Ruff は lint と import 整理だけに使いましょう。同じ Python ファイルに対して Ruff と Black の両方をデフォルトフォーマッタにしないでください。


Python コーディング規約(PEP 8)

Section titled “Python コーディング規約(PEP 8)”

PEP 8 は Python の公式コーディング規約です。特に大切なポイントは次のとおりです。

# 変数と関数: 小文字 + アンダースコア(snake_case)
feature_name = "ログイン API"
def calculate_average_latency(latencies):
return sum(latencies) / len(latencies)
# クラス: 先頭を大文字(PascalCase)
class DataProcessor:
def __init__(self, source: str):
self.source = source
# 定数: すべて大文字 + アンダースコア
MAX_RETRY = 3
DEFAULT_TIMEOUT = 30
API_BASE_URL = "https://api.example.com"
# "非公開" 属性: 先頭にアンダースコア
class MyClass:
def __init__(self):
self._internal_state = None
# 関数の間は空行 2 行
def function_one():
return "function one"
def function_two():
return "function two"
# クラスの間は空行 2 行
class ClassOne:
value = 1
class ClassTwo:
value = 2
# 演算子の前後にスペースを入れる
x = 1 + 2 # ✅
x = 1+2 # ❌
# カンマの後ろにスペースを入れる
items = [1, 2, 3] # ✅
items = [1,2,3] # ❌
# 関数引数のデフォルト値の前後にはスペースを入れない。
# 2 つ目は構文としては動きますが、PEP 8 では推奨されません。
def func(x=10): # ✅
return x
def func_not_recommended(x = 10): # ❌ スタイル上は非推奨
return x
# 1 行は 79 文字以内(チームのルールによっては 88 や 120 文字)
# 長い行は括弧で改行する
result = (
first_variable
+ second_variable
+ third_variable
)
# 引数が多い関数
def complex_function(
param1: str,
param2: int,
param3: float = 0.0,
param4: bool = True,
) -> dict:
return {
"param1": param1,
"param2": param2,
"param3": param3,
"param4": param4,
}

よい docstring があると、ほかの人も未来の自分もコードをすばやく理解できます。

def train_model(
data: list[dict],
epochs: int = 100,
learning_rate: float = 0.001,
batch_size: int = 32
) -> dict[str, float]:
"""
モデルを学習し、学習指標を返す。
Args:
data: 学習データのリスト。各要素は1つのサンプル辞書
epochs: 学習回数。デフォルトは 100
learning_rate: 学習率。デフォルトは 0.001
batch_size: バッチサイズ。デフォルトは 32
Returns:
学習指標を含む辞書。例:
{"accuracy": 0.95, "loss": 0.05}
Raises:
ValueError: data が空のとき
RuntimeError: GPU が使えないとき
Example:
>>> result = train_model(data, epochs=50)
>>> print(result["accuracy"])
0.95
"""
if not data:
raise ValueError("学習データは空にできません")
# 実際の学習処理の代わりに、最小限の評価指標を返す
total = sum(len(str(item)) for item in data)
accuracy = min(0.95, 0.6 + total / 1000)
return {"accuracy": accuracy, "loss": 1 - accuracy}

練習 1: 古いコードに型注釈を追加する

Section titled “練習 1: 古いコードに型注釈を追加する”

次のコードに、完全な型注釈を追加してください。

def process_tasks(tasks, max_hours):
results = []
for task in tasks:
if task["hours"] <= max_hours:
results.append({
"name": task["name"],
"hours": task["hours"],
"ready": True
})
return results
def calculate_stats(numbers):
if not numbers:
return None
return {
"mean": sum(numbers) / len(numbers),
"max": max(numbers),
"min": min(numbers),
"count": len(numbers)
}

練習 2: コード品質ツールをインストールして使う

Section titled “練習 2: コード品質ツールをインストールして使う”
Terminal window
# 1. ruff をインストールする
pip install ruff
# 2. 形式に問題がある Python ファイルを作る
# 3. ruff check を実行して問題を確認する
# 4. ruff format を実行して自動整形する
# 5. 整形前後の差分を比べる

学んだルールをすべて使って、次の「よくない」コードを書き直してください。

# よくないコード
def f(l,n):
r=[]
for x in l:
if x>n:r.append(x)
return r
def g(d):
s=0
for k in d:s+=d[k]
return s/len(d)

条件:

  1. わかりやすい名前にする
  2. 型注釈を追加する
  3. docstring を追加する
  4. PEP 8 に従う

参考実装と解説
  1. 既存関数には、たとえば process_tasks(tasks: list[dict[str, int | str]], max_hours: int) -> list[dict[str, object]]calculate_stats(numbers: Sequence[float]) -> dict[str, float] | None のように、入力構造と空リスト時の戻り値がわかる型を付けます。
  2. ruff はまず ruff check で問題を確認し、そのあと ruff format で整形し、最後に差分を見る流れが扱いやすいです。lint と整形を分けると、何が直ったかを説明しやすくなります。
  3. 書き直し後のコードは、意味のある関数名、型注釈、docstring、PEP 8 の空白を満たす必要があります。平均値を返す関数では、空入力でゼロ除算しないようにすることも大切です。
ツール/概念役割おすすめ度
型注釈引数と戻り値の型を示す強く推奨
PEP 8Python のコード規約必ず守る
black / ruff formatコードを自動整形する強く推奨
ruffコード品質をチェックする強く推奨
mypy静的型チェックを行う推奨
docstringドキュメント文字列公開関数には必須