Pythonのコメントの書き方とDocstring徹底解説|可読性・保守性を高めるベストプラクティス

コードは未来の自分や仲間へのメッセージです。

PythonではコメントとDocstringが可読性・保守性を大きく左右します。

本記事ではPEP 8/PEP 257に沿った原則から、Google/NumPy/reSTといったDocstringスタイル、ツール連携までを体系的に解説し、悪い例も交えて実務で使える指針をまとめます。

目次
  1. Pythonコメントの基本:書き方と種類(#、インライン、ブロック)
  2. PEP 8に沿ったコメントのベストプラクティス(可読性・保守性)
  3. Docstringの基礎:PEP 257と配置(モジュール/クラス/関数)
  4. Docstringの書式スタイルとサンプル(Google/NumPy/reST)
  5. コメントとDocstringで可読性・保守性を高めるテクニック
  6. 悪い例とアンチパターン:避けるべきコメント/Docstring
  7. まとめ

Pythonコメントの基本:書き方と種類(#、インライン、ブロック)

Pythonのコメントは大きく分けて「単一行コメント」「インラインコメント」「ブロックコメント(段落化した複数行の#)」があります。

PythonにはC言語のような本来のブロックコメント構文はありません。

用途と射程を明確にし、過不足のない最小限で書くことが基本です。

単一行コメントの書き方(#)と原則:Python コメントの最小限・明確化

単一行コメントは行頭に#を書き、次に半角スペースを入れて自然文で書きます。

読み手が「なぜ?」を理解できる内容にすることがポイントです。

Python
# 入力がNoneのときは既定のタイムアウトを使う(外部APIが閾値を要求するため)
def compute_timeout(user_timeout: int | None, default: int = 5) -> int:
    if user_timeout is None:
        return default
    return max(0, user_timeout)

コメントはコードの重複説明ではなく、設計上の判断や業務上の背景などコードからは読めない文脈を補います。

インラインコメントの注意点(行末の#、空白、簡潔さ)

インラインコメントはコードの右側に#で書きます。

前に2つのスペースを置き、短く保ちます。

複雑な説明は行頭コメントやDocstringへ移してください。

Python
threshold = 0.8  # ROC-AUCの実験結果に基づく暫定値
total = price * qty  # 税抜合計

次のような重複や冗長さは避けます。

Python
total = price * qty  # 価格に数量を掛ける ← これはコードを繰り返しているだけ

ブロックコメントの構成(段落化・インデント・箇条書き)

ブロックコメントは複数行の単一行コメントを段落として構成します。

対象コードのインデントに合わせ、最初の行は要約、続く行で詳細や前提、例外を説明します。

箇条書きは本当に必要なときだけ使います。

Python
def fetch_user(user_id: str) -> dict:
    # ユーザー情報の取得方針
    # - まずローカルキャッシュを参照(TTL=60秒)
    # - キャッシュにない場合はHTTPでフェッチ
    # - 404はNoneを返さず例外を送出して上位で扱う(呼び出し側でログ集約)
    ...

TODO/FIXME/HACKなどタグの使い方と検索性

タグは緊急度・性質で使い分けると検索性が上がります。

  • TODO: 未実装や改善予定
  • FIXME: 既知のバグ
  • HACK: 苦肉の策、暫定対応
  • NOTE: 重要だが即対応不要の覚書
  • DEPRECATED: 廃止予定
Python
# TODO: v2でページング対応を実装する(API側の対応が完了してから)
# FIXME: タイムゾーンの扱いが不正確(夏時間切替で1時間ずれる)
# HACK: 互換性維持のため空文字をNoneに変換している

タグはripgrep (rg), git grep, IDEの「TODOビュー」などで収集できるよう、綴りを統一します。

コメントでコードを無効化しない方針(条件分岐・テストで代替)

コメントアウトでコードを「仮停止」するのは履歴や検索性を損ねます。

Git履歴に任せ、条件分岐やフラグで制御しましょう。

Python悪い例
# old_validate(user)  # 古い検証は停止
new_validate(user)

Python良い例(機能フラグで切り替え)
def validate(user, *, use_new: bool = True) -> None:
    if use_new:
        new_validate(user)
    else:
        legacy_validate(user)

テストで期待挙動を固定化することで、意図しない回帰を防ぎます。

PEP 8に沿ったコメントのベストプラクティス(可読性・保守性)

PEP 8はコメントの位置や長さ、文体を含めた包括的なガイドを示します。

チームで合意し、リンターで自動チェックすることで、ばらつきを抑えられます。

「何を」ではなく「なぜ」を説明するコメント

「何を」はコードが語ります。

「なぜ」はコードだけでは伝わりません。

Python
# なぜ: BigQueryは1クエリあたりの結果行数に上限があるため、日次で分割して取得する
def fetch_daily_partitioned(...):
    ...

行長ガイドライン(コメント72桁・コード79桁)と言語統一

PEP 8では原則としてコメント・Docstringは72桁以内、コードは79桁以内を推奨します。

自動整形ツールBlackはデフォルト88桁ですが、チームの規約で統一してください。

プロジェクト内での言語(日本語または英語)も統一し、混在による誤読を避けます。

命名・型ヒントと整合するコメント(重複説明を避ける)

型ヒントで表現できることはコメントに繰り返さない方が読みやすくなります。

Python
from collections.abc import Iterable

def topk(items: Iterable[int], k: int) -> list[int]:
    """上位k件を降順で返します。重複は保持します。"""
    ...

ここでIterable[int]list[int]が意味を十分に伝えるため、「itemsは整数のリストです」といった重複説明は不要です。

迷うならコメントよりリファクタリングで意図を表現

長いコメントが必要に感じたら、関数の分割や命名見直しで意図がコードに現れるようにします。

Python悪い例
# ここでユーザーがアクティブかつ課金中で...(長い説明)
if user.is_active and user.plan.is_paid and not user.is_suspended:
    ...

Python良い例
def is_billable(user: User) -> bool:
    return user.is_active and user.plan.is_paid and not user.is_suspended

if is_billable(user):
    ...

チーム規約とリンター設定でベストプラクティスを定着

設定はリポジトリにコミットして自動化します。

toml
# pyproject.toml
[tool.ruff]
line-length = 88
select = ["E", "F", "W", "D"]  # pydocstyleルールを含める
ignore = ["D104"]              # パッケージの__init__にDocstring不要など

[tool.ruff.pydocstyle]
convention = "google"          # または "numpy", "pep257"

[tool.black]
line-length = 88

CIでruffを実行し、PRで逸脱を検出します。

Docstringの基礎:PEP 257と配置(モジュール/クラス/関数)

Docstringは「ドキュメントとして公開する説明」です。

PEP 257は書き方・配置の原則を定めています。

記述位置と順序:定義直下に三重引用符でDocstring

Docstringは定義の直下に"""で書きます。

Python
"""データ処理ユーティリティ群。

このモジュールは入出力と前処理を提供します。
"""

def normalize(text: str) -> str:
    """前後空白を削除し小文字化します。"""
    return text.strip().lower()

1行Docstringと複数行Docstring(要約→空行→詳細)

要約は命令形の短文で。

詳細が続く場合は空行を挟みます。

Python
def slugify(text: str) -> str:
    """URLセーフなスラッグに変換する。"""

def connect_dsn(dsn: str, timeout: float = 3.0) -> "Connection":
    """DSNから接続を確立する。

    接続が確立できない場合はTimeoutErrorを送出します。
    再試行は行いません。
    """

公開APIと内部関数でのDocstringの深さの使い分け

公開APIは外部利用者の視点で前提や制約、例外、計算量などを明確にします。

内部関数は過度に詳述せず、テストと命名で意図を伝えます。

非公開は先頭にアンダースコア_を付け、必要最小限のDocstringに留めます。

__init__とクラスDocstringの役割分担

クラスDocstringはクラス全体の目的や利用方法を説明し、__init__はパラメータの意味を説明します。

SphinxのautodocではクラスDocstringに__init__の引数説明をマージする設定もありますが、原則は役割分担を意識します。

Python
class Cache:
    """LRU方式のメモリキャッシュ。

    スレッドセーフではありません。プロセス内での軽量用途に限ります。
    """

    def __init__(self, capacity: int = 128) -> None:
        """キャッシュ容量を指定します。

        capacity: 最大エントリ数。0以下はValueError。
        """

Docstringの書式スタイルとサンプル(Google/NumPy/reST)

チームで1つのスタイルを選び、リンターで統一します。

ここでは代表的な3種類を概説します。

GoogleスタイルDocstringの書き方(Args/Returns/Raises/Examples)

簡潔で読みやすく、広く使われます。

Python
def moving_average(xs: list[float], window: int) -> list[float]:
    """移動平均を計算します。

    Args:
        xs: 元データの数列。
        window: 窓幅。1以上である必要があります。

    Returns:
        各位置の移動平均値のリスト。長さはlen(xs) - window + 1。

    Raises:
        ValueError: windowが1未満、またはwindowがlen(xs)を超える場合。

    Examples:
        >>> moving_average([1, 2, 3, 4], 2)
        [1.5, 2.5, 3.5]
    """
    if window < 1 or window > len(xs):
        raise ValueError("invalid window")
    return [
        sum(xs[i : i + window]) / window
        for i in range(len(xs) - window + 1)
    ]

NumPyスタイルDocstringの書き方(Parameters/Returns/Examples)

科学技術計算で普及。

セクション見出しの下線が特徴です。

Python
def zscore(x):
    """標準化した配列を返す。

    Parameters
    ----------
    x : array_like
        標本データ。

    Returns
    -------
    z : ndarray
        平均0、分散1に正規化されたデータ。

    Examples
    --------
    >>> import numpy as np
    >>> zscore(np.array([1, 2, 3])).round(3)
    array([-1.225,  0.   ,  1.225])
    """
    import numpy as np
    x = np.asarray(x, dtype=float)
    return (x - x.mean()) / x.std(ddof=0)

reStructuredText/Sphinxの役割と採用基準

reSTはSphinxが解釈し、HTML/PDFドキュメントを生成します。

型やリンクの自動解決に強みがあります。

Python
def read(path: str, encoding: str = "utf-8") -> str:
    """ファイルを読み込みます。

    :param str path: ファイルパス。
    :param str encoding: 文字エンコーディング。
    :returns: ファイル内容。
    :rtype: str
    :raises FileNotFoundError: ファイルが存在しない場合。
    """
    with open(path, "r", encoding=encoding) as fp:
        return fp.read()

SphinxでAPIリファレンスを生成する予定があるならreST、読み書きの容易さを重視するならGoogleやNumPyスタイルを選ぶとよいです。

例外・戻り値・制約の明記と型ヒントの併用

型ヒントはIDE支援やドキュメント生成と相性が良い一方、制約や境界条件はDocstringで明記します。

Python
from collections.abc import Sequence

def percentile(xs: Sequence[float], p: float) -> float:
    """百分位数を返します。

    0 <= p <= 100。xsが空ならValueError。

    Raises:
        ValueError: xsが空、またはpが範囲外。
    """
    if not xs or not (0 <= p <= 100):
        raise ValueError("invalid arguments")
    ...

コメントとDocstringで可読性・保守性を高めるテクニック

ツールを活用して標準化し、ドキュメントとテストを近づけると保守コストを下げられます。

Sphinx/pdocで自動ドキュメント生成(目次・型リンク)

Sphinxのautodocintersphinxで型リンクや目次を自動化します。

Python
# docs/conf.py
extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.napoleon",      # Google/NumPyスタイルをreSTに変換
    "sphinx.ext.intersphinx",
    "sphinx_autodoc_typehints", # 型ヒントをDocstringに反映
]
intersphinx_mapping = {"python": ("https://docs.python.org/3", {})}
autodoc_typehints = "description"  # 引数説明に型を表示

軽量に済ませたい場合はpdocも有力です。

型ヒントとDocstringから即座にWebドキュメントを生成できます。

Shell
pdoc -o docs html your_package

pydocstyle/flake8-docstrings/ruffでDocstringルールチェック

ruff一つでpydocstyle相当のチェックを有効化できます。

Shell
# ルールチェック
ruff check .

# 自動修正可能なものは修正
ruff check . --fix

Google/NumPyスタイルを指定して体裁を統一します(前述のpyproject参照)。

doctestやExamplesで仕様をテストとして担保

Examplesセクションにdoctestを埋め込み、仕様を実行可能なサンプルとして維持します。

以下は自己テスト可能なスクリプト例です。

Python
# file: textutil.py
def normalize(name: str) -> str:
    """前後空白を削除し小文字にします。

    Examples:
        >>> normalize(" Foo  ")
        'foo'
    """
    return name.strip().lower()


if __name__ == "__main__":
    import doctest

    # doctestを実行して結果を表示
    failures, tests = doctest.testmod(optionflags=doctest.ELLIPSIS)
    print(f"{tests} tests, {failures} failures")

    # 動作確認の通常出力
    print(normalize(" Bar  "))
実行結果
1 tests, 0 failures
bar

長文の設計意図はREADME/ADRへ分離し、Docstringは要点化

アルゴリズム選定理由、アーキテクチャ設計、非機能要件などはREADMEやADR(Architecture Decision Record)にまとめ、Docstringには利用者が知るべき要点だけを残します。

DocstringはAPIリファレンス、README/ADRは設計記録という役割分担が保守性を高めます。

悪い例とアンチパターン:避けるべきコメント/Docstring

コメントとDocstringが存在するだけでは十分ではありません。

品質を損なうパターンを避けます。

コードの反復説明・誤情報・放置されたコメント

反復説明はノイズ、誤情報は害です。

更新と同期を心掛けます。

Python悪い例
count = len(items)  # itemsの長さを数える ← 反復
# 2021-01-01: この機能は使われていない(実際は使われている)

Python良い例
# 外部仕様: 空入力は0件として扱う(API契約より)
count = len(items)

パラメータ未記載・更新漏れのDocstring

引数を追加したのにDocstringを更新しないのは危険です。

リンターで検出し、PRで修正します。

Python悪い例
def load(path: str, *, cache: bool = True) -> str:
    """ファイルを読み込みます。"""  # cacheの説明がない

Python良い例(Googleスタイル)
def load(path: str, *, cache: bool = True) -> str:
    """ファイルを読み込みます。

    Args:
        path: ファイルパス。
        cache: キャッシュを利用するか。デフォルトTrue。
    """

日本語/英語の混在や機械翻訳の直貼り

プロジェクト内で言語を統一します。

外部向けは英語、社内向けは日本語など、対象読者を基準に決めます。

機械翻訳は校正し、専門用語の用字をプロジェクト用語集に合わせます。

実装逐語解説より意図・前提・副作用・計算量を記述

「何が起きるか」はコードが示します。

Docstringやコメントでは次の点を中心に書くと価値が高まります。

  • 意図(なぜこのアルゴリズムか)
  • 前提(入力の分布、単調性、ソート済みなど)
  • 副作用(グローバル状態、I/O、キャッシュ)
  • 計算量(時間・空間オーダー、境界条件)
Python
def dedup_sorted(xs: list[int]) -> list[int]:
    """ソート済み配列の重複を削除して返す。

    前提: xsは昇順ソート済み。O(n)で走査し、安定性は考慮しない。
    破壊的変更は行わない(新しいリストを返す)。
    """
    ...

まとめ

コメントとDocstringは「最小限で明確に、コードにない文脈を補う」ことが原則です。

PEP 8/257の基本を守り、行長や言語を統一し、Google/NumPy/reSTのいずれかのスタイルでDocstringを一貫させましょう。

型ヒントと命名で表現できることはコードに寄せ、コメントでは「なぜ」を語る。

Sphinxやpdocで自動ドキュメントを整備し、ruffやpydocstyleでルールを継続的にチェック、doctestやExamplesで仕様を実行可能な形に保ちます。

悪い例を避け、README/ADRと役割分担することで、読みやすく壊れにくいPythonコードベースを育てられます。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!