3.6 KiB
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()