0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonパッケージの公開APIを明確化する

0
Posted at

Pythonパッケージの公開APIを明確化する:__all__属性でコードの意図を伝える設計

サマリー

ビジネス価値: パッケージの意図的な設計により、開発チームの協業効率が向上し、保守性の高いシステム基盤を構築

開発効率: 明示的なAPI定義により、開発者が迷わずに適切なモジュールを利用でき、コードレビューの品質も向上

品質担保: Python言語仕様に準拠したコード構造により、静的解析ツールとの親和性が高まり、潜在的なバグを未然に防止

背景

図面評価システムの開発において、Pythonパッケージの構造が複雑化するにつれて、「どのモジュールが外部から利用されることを想定しているのか」「どの機能が内部実装なのか」という境界が曖昧になってきました。

特に、FastAPIを活用したWebアプリケーションでは、APIルーター、ドメインロジック、インフラストラクチャ層など、複数のレイヤーにまたがるパッケージ構造を持つため、開発者が「このモジュールをimportしても良いのか?」と迷うケースが頻発していました。

この課題を解決するため、Python言語の慣習に従った明示的なAPI定義を導入することにしました。

Before/After(擬似コード)

変更前:課題があるコード(Antipattern)

# api/__init__.py
# 空ファイル(何がパブリックAPIかが不明)

# api/v1/__init__.py
from fastapi import APIRouter
from .endpoints import health, evaluator

# ルーターを作成
api_router = APIRouter()
api_router.include_router(health.router, prefix="/health")
api_router.include_router(evaluator.router, prefix="/evaluator")

# どれが公開APIかが不明確

変更後:改善されたコード(Pattern)

# api/__init__.py
"""API layer package"""

# 明示的に「パブリックなオブジェクトは存在しない」ことを宣言
__all__: list[str] = []

# api/v1/__init__.py
"""API v1 router package"""

from fastapi import APIRouter
from .endpoints import health, evaluator

# ルーターを作成
api_router = APIRouter()
api_router.include_router(health.router, prefix="/health")
api_router.include_router(evaluator.router, prefix="/evaluator")

# パブリックAPIを明示的に宣言
__all__ = [
    "api_router",
]

# api/v1/endpoints/__init__.py
"""API v1 endpoints package"""

# 詳細な説明コメントで設計意図を明確化
# このパッケージは内部モジュール群を含みますが、
# 外部からは api_router 経由での利用を想定しています
__all__: list[str] = []

技術的詳細

アーキテクチャ選定の思考プロセス

1. 他の手法との比較検討

当初、以下のような代替案も検討しました:

  • プライベートモジュール化(_プレフィックス): モジュール名にアンダースコアを付けることでプライベート化する手法

    • 却下理由: 既存のディレクトリ構造を大幅に変更する必要があり、影響範囲が広すぎる
  • 型チェッカー向けのstubファイル活用: .pyiファイルでインターフェースを定義する手法

    • 却下理由: 運用コストが高く、実装と定義の同期が困難

2. __all__属性を選択した理由

  • Python標準の慣習: PEP 8で推奨される、言語仕様に準拠したアプローチ
  • ツールチェーンとの親和性: IDE、linter、型チェッカーが標準で対応
  • 段階的導入: 既存コードを壊すことなく、パッケージ単位で順次適用可能

実装戦略

レイヤー別の適用方針

domain/           → __all__ = [](ドメインオブジェクトは個別import推奨)
application/      → __all__ = [](アプリケーションサービスは直接importしない)
infrastructure/   → __all__ = [](インフラ実装は隠蔽)
api/v1/          → __all__ = ["api_router"](ルーターのみ公開)

この構造により、Clean Architectureの依存関係の向きを、import文レベルでも強制できるようになりました。

品質担保の仕組み

全ての変更に対して以下の自動チェックを実行:

  • コードフォーマッター(Black): 一貫したコードスタイルの維持
  • リンター(flake8): コーディング規約の遵守
  • 型チェッカー(mypy): 型安全性の確保
  • テストスイート: 網羅的な自動テストによる動作保証

学び・知見

再利用可能な設計原則

1. 明示性の原則

# Good: 意図が明確
__all__ = ["PublicClass"]

# Bad: 暗黙的
# __all__の定義なし(すべてがpublicに見える)

2. 段階的な情報開示

  • パッケージレベルでの粗粒度な制御
  • モジュールレベルでの細粒度な制御
  • コメントによる設計意図の補完

3. ツールチェーン活用の重要性
単なるコーディング規約ではなく、開発ツールが自動で検証できる形式で制約を表現することで、レビューコストを削減し、品質を向上させることができます。

運用における教訓

  • 空の__all__も価値がある: 「何もエクスポートしない」という意図の明示化
  • コメントでの補完: __all__だけでは伝わらない設計思想を、コメントで補完することの重要性
  • 型アノテーションの活用: __all__: list[str] = []のように型情報も含めることで、開発体験が向上

免責事項

本記事は技術的知見の共有を目的としており、実際のプロジェクトから一部抽象化・簡略化した内容となっています。具体的なビジネス要件や制約により、最適解は変わる可能性があります。また、本記事はGitHub Pull Requestから自動で生成した下書きをベースに作成しました。


Generated by Claude AI on 2026-01-22 (UTC)

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?