From 0055c667314bd3c645c1fc39144f7ebd4046e62a Mon Sep 17 00:00:00 2001 From: "ry.yamafuji" Date: Sat, 30 Aug 2025 14:30:20 +0900 Subject: [PATCH] =?UTF-8?q?lib=E3=83=95=E3=82=A9=E3=83=AB=E3=83=80?= =?UTF-8?q?=E5=90=8D=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docments/DIコンテナ.md | 116 ++++++++++++++++++++ src/{libs => lib}/__init__.py | 0 src/{libs => lib}/custom_logger.py | 30 ++++- src/lib/di_container.py | 22 ++++ src/{libs => lib}/singleton.py | 9 ++ timeSchedule.py => src/lib/time_schedule.py | 0 src/{libs => lib}/time_watch.py | 0 7 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 docments/DIコンテナ.md rename src/{libs => lib}/__init__.py (100%) rename src/{libs => lib}/custom_logger.py (64%) create mode 100644 src/lib/di_container.py rename src/{libs => lib}/singleton.py (75%) rename timeSchedule.py => src/lib/time_schedule.py (100%) rename src/{libs => lib}/time_watch.py (100%) diff --git a/docments/DIコンテナ.md b/docments/DIコンテナ.md new file mode 100644 index 0000000..c61ec46 --- /dev/null +++ b/docments/DIコンテナ.md @@ -0,0 +1,116 @@ +# [Python]DIコンテナの利用ガイドライン + +## DIコンテナのメリット + +- 依存性の解決を通じてクラスの自立性を高める +- テストしやすくなる +- 構成情報をコンテナで一括管理できる +- モジュール化により小規模コードの再利用性が向上 + +## ユースケース + +- Logger や Config などの共通サービスの渡し込み +- DBクライアントなどの再利用 +- サービス層の分離 +- Flask/FastAPI との統合 + +## ライブラリの選定 + +### punq +- 最簡易な軽量DIコンテナ +- Python標準の型ヒントで自動組み立て +- 小規模スクリプトに最適 + +```py +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なインスタンス管理に対応した構造です。 + +```python +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() +``` + + diff --git a/src/libs/__init__.py b/src/lib/__init__.py similarity index 100% rename from src/libs/__init__.py rename to src/lib/__init__.py diff --git a/src/libs/custom_logger.py b/src/lib/custom_logger.py similarity index 64% rename from src/libs/custom_logger.py rename to src/lib/custom_logger.py index 48e219e..bde8c74 100644 --- a/src/libs/custom_logger.py +++ b/src/lib/custom_logger.py @@ -2,7 +2,30 @@ import logging import functools from .singleton import Singleton +# ANSIカラーコード定義 +RESET = "\033[0m" +RED = "\033[31m" +YELLOW = "\033[33m" +GREEN = "\033[32m" +BLUE = "\033[34m" + +class ColorFormatter(logging.Formatter): + COLORS = { + logging.DEBUG: BLUE, + logging.INFO: GREEN, + logging.WARNING: YELLOW, + logging.ERROR: RED, + logging.CRITICAL: RED + "\033[1m", # 太字赤 + } + + def format(self, record): + color = self.COLORS.get(record.levelno, RESET) + message = super().format(record) + return f"{color}{message}{RESET}" + + class CustomLogger(Singleton): + def __init__(self, name='main', log_file=None, level=logging.INFO): if hasattr(self, '_initialized') and self._initialized: self.logger.setLevel(level) @@ -12,10 +35,15 @@ class CustomLogger(Singleton): self.logger.setLevel(level) self.logger.propagate = False - formatter = logging.Formatter( + # formatter = logging.Formatter( + # '%(asctime)s %(levelname)s [%(filename)s:%(lineno)3d]: %(message)s' + # ) + formatter = ColorFormatter( '%(asctime)s %(levelname)s [%(filename)s:%(lineno)3d]: %(message)s' ) + + # Console handler ch = logging.StreamHandler() ch.setFormatter(formatter) diff --git a/src/lib/di_container.py b/src/lib/di_container.py new file mode 100644 index 0000000..062cbd8 --- /dev/null +++ b/src/lib/di_container.py @@ -0,0 +1,22 @@ +from .singleton import SingletonMeta + + +class DiContainer(metaclass=SingletonMeta): + 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() diff --git a/src/libs/singleton.py b/src/lib/singleton.py similarity index 75% rename from src/libs/singleton.py rename to src/lib/singleton.py index 2313286..cf99cb7 100644 --- a/src/libs/singleton.py +++ b/src/lib/singleton.py @@ -18,3 +18,12 @@ class Singleton(object): if cls not in cls._instances: # ダブルチェック cls._instances[cls] = super(Singleton, cls).__new__(cls) return cls._instances[cls] + + +class SingletonMeta(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] \ No newline at end of file diff --git a/timeSchedule.py b/src/lib/time_schedule.py similarity index 100% rename from timeSchedule.py rename to src/lib/time_schedule.py diff --git a/src/libs/time_watch.py b/src/lib/time_watch.py similarity index 100% rename from src/libs/time_watch.py rename to src/lib/time_watch.py