python-common-code/docments/DIコンテナ.md
2025-08-30 14:30:20 +09:00

3.6 KiB

[Python]DIコンテナの利用ガイドライン

DIコンテナのメリット

  • 依存性の解決を通じてクラスの自立性を高める
  • テストしやすくなる
  • 構成情報をコンテナで一括管理できる
  • モジュール化により小規模コードの再利用性が向上

ユースケース

  • Logger や Config などの共通サービスの渡し込み
  • DBクライアントなどの再利用
  • サービス層の分離
  • Flask/FastAPI との統合

ライブラリの選定

punq

  • 最簡易な軽量DIコンテナ
  • Python標準の型ヒントで自動組み立て
  • 小規模スクリプトに最適
import punq

class Logger:
    def log(self, msg):
        print(f"[LOG] {msg}")

class Service:
    def __init__(self, logger: Logger):
        self.logger = logger

    def run(self):
        self.logger.log("Running service!")

# コンテナに登録
container = punq.Container()
container.register(Logger)
container.register(Service)

# 自動で依存解決してインスタンス化
svc = container.resolve(Service)
svc.run()

injector

  • Google Guice に着想を得た正統派DI
  • singleton scope や module 構築が可能
  • Flask/FastAPI との連携実績が多い
  • 中央管理型プロジェクトに向いている

機能比較表

機能カテゴリ 内容例 punq injector
型ヒントによる自動解決 __init__(db: DBClient)のような注入
スコープ管理 singleton, transient の切り替え
明示的なバインディング register(ILogger, ConsoleLogger)
モジュール構造化 DI定義をクラス単位で管理
循環依存の検出 循環関係を検出・警告
テスト用差し替え mock の注入や切替
パフォーマンス 実装が軽く高速 高速 中程度

独自実装する場合のサンプルコード

以下はシンプルなDIコンテナの実装例で、singletonなインスタンス管理に対応した構造です。

class Container:
    def __init__(self):
        self._registrations = {}
        self._singletons = {}

    def register(self, interface, implementation, singleton=True):
        self._registrations[interface] = (implementation, singleton)

    def resolve(self, interface):
        impl, singleton = self._registrations.get(interface, (None, None))
        if impl is None:
            raise ValueError(f"No registration for {interface}")

        if singleton:
            if interface not in self._singletons:
                self._singletons[interface] = impl()
            return self._singletons[interface]
        else:
            return impl()

# 利用例
class ILogger:
    def log(self, msg):
        raise NotImplementedError

class ConsoleLogger(ILogger):
    def log(self, msg):
        print(f"[LOG] {msg}")

class App:
    def __init__(self, logger: ILogger):
        self.logger = logger

    def run(self):
        self.logger.log("App is running")

# DIコンテナの使用
container = Container()
container.register(ILogger, ConsoleLogger, singleton=True)
logger = container.resolve(ILogger)

app = App(logger)
app.run()