はじめに
DI研究の一環です。
Pythonの標準機能のみでDIを目指します。
コード
先に全体を乗せ、後で詳細に説明していきます。
実装
class DependencyInjection:
def __init__(self, declarations):
self.di_container = dict()
for c in declarations:
init_param = c.__init__.__annotations__
correspond = {
k: self.di_container[v]
for k, v in init_param.items()
}
self.di_container[c] = c(**correspond)
class A:
def __init__(self):
self.iam = 'class A'
self.di = None
class B:
def __init__(self, cls_a: A):
self.iam = 'class B'
self.di = cls_a
class C:
def __init__(self, cls_b: B):
self.iam = 'class C'
self.di = cls_b
class D:
def __init__(self, cls_b: B, cls_c: C):
self.iam = 'class D'
self.di = [cls_b, cls_c]
di = DependencyInjection([A, B, C, D])
print(di.di_container)
print()
for k, v in di.di_container.items():
print(v.iam)
print(v.di)
DependencyInjection
がDI機能を担うクラスで、クラスA, B, C, Dがそれぞれ依存関係にあるクラスの想定です。
DependencyInjection
に依存関係にあるクラス群を依存関係順にリストで渡すことでDIされるようになっています。
このコードを実行すると以下の出力結果になります。
出力結果
{
<class '__main__.A'>: <__main__.A object at 0x000001FA6FF0BD00>,
<class '__main__.B'>: <__main__.B object at 0x000001FA6FF0BC70>,
<class '__main__.C'>: <__main__.C object at 0x000001FA6FF0BC10>,
<class '__main__.D'>: <__main__.D object at 0x000001FA6FF0BBB0>
}
class A
None
class B
<__main__.A object at 0x000001FA6FF0BD00>
class C
<__main__.B object at 0x000001FA6FF0BC70>
class D
[<__main__.B object at 0x000001FA6FF0BC70>, <__main__.C object at 0x000001FA6FF0BC10>]
メモリIDは実行ごとに異なるかと思いますが、IDを見ていただけばDIされていることがわかるかと思います。
説明
DI処理のメイン部分となる、 DependencyInjection
について説明します。
class DependencyInjection:
def __init__(self, declarations):
self.di_container = dict()
for c in declarations:
init_param = c.__init__.__annotations__
correspond = {
k: self.di_container[v]
for k, v in init_param.items()
}
self.di_container[c] = c(**correspond)
受け取ったクラスのリスト declarations
を
correspond = {
k: self.di_container[v]
for k, v in init_param.items()
}
で引数名と引数の型のインスタンスの辞書を作成。( init_param
については後述しますので、ここはそういうものとして一旦流してください。)
self.di_container[c] = c(**correspond)
辞書を展開しつつインスタンスを作成し、そのインスタンスを self.di_container
にクラスと対応付けてマッピングします。
これのため、 依存関係にあるクラス群を依存関係順にリストで渡す 必要があります。
試していないのでできるかわかりませんが、リストの順番に依存しないDIを解決する一案として両端キューを用います。
リストの先頭を取り出し、すでに self.di_container
にクラスがあれば注入、なければリストの末尾に戻す、01-BFSのような方法を草案としてあります。
とはいえ、これをやるならやるで、ループ検知もやる必要があると考えています。難しい。
次、
init_param = c.__init__.__annotations__
について、Pythonのinspectの__annotations__
を用いています。
これは以下の振る舞いをします。
from typing import (List, Dict)
def f (n: int, str_list: List[str], d: Dict[str, bool]):
pass
f.__annotations__ # {'n': <class 'int'>, 'str_list': typing.List[str], 'd': typing.Dict[str, bool]}
このように、引数名とその型を辞書形式で取得できます。
これを知ったときPythonでDIをやってみようと思い立ち、本記事につながった次第です。
従って、
correspond = {
k: self.di_container[v]
for k, v in init_param.items()
}
が引数名とその型の辞書となるわけです。
おわりに
DIに関しての進捗報告でした。
このDI部分をクラスにしようと考えたのは、このDependencyInjectionを受け取る親DIクラスを作れたらと考えたからです。イメージとしてはAngularのNgModuleです。今回はやりません。また別の機会に回します。
最後に、Pythonのinspectや特殊メソッドが面白いです。終わり。