PythonでDIするコードを書いてみました。

# はじめに

DI研究の一環です。
Pythonの標準機能のみでDIを目指します。

# コード

先に全体を乗せ、後で詳細に説明していきます。

## 実装

```py
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` について説明します。

```py
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` を

```py
correspond = {
  k: self.di_container[v]
  for k, v in init_param.items()
}
```

で引数名と引数の型のインスタンスの辞書を作成。( `init_param` については後述しますので、ここはそういうものとして一旦流してください。)

```py
self.di_container[c] = c(**correspond)
```

辞書を展開しつつインスタンスを作成し、そのインスタンスを `self.di_container ` にクラスと対応付けてマッピングします。
これのため、 **依存関係にあるクラス群を依存関係順にリストで渡す** 必要があります。
試していないのでできるかわかりませんが、リストの順番に依存しないDIを解決する一案として両端キューを用います。
リストの先頭を取り出し、すでに `self.di_container` にクラスがあれば注入、なければリストの末尾に戻す、01-BFSのような方法を草案としてあります。
とはいえ、これをやるならやるで、ループ検知もやる必要があると考えています。難しい。

次、

```py
init_param = c.__init__.__annotations__
```

について、Pythonの[inspect](https://docs.python.org/ja/3/library/inspect.html)の`__annotations__`を用いています。
これは以下の振る舞いをします。

```py
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をやってみようと思い立ち、本記事につながった次第です。
従って、

```py
correspond = {
  k: self.di_container[v]
  for k, v in init_param.items()
}
```

が引数名とその型の辞書となるわけです。

# おわりに

DIに関しての進捗報告でした。
このDI部分をクラスにしようと考えたのは、このDependencyInjectionを受け取る親DIクラスを作れたらと考えたからです。イメージとしてはAngularの[NgModule](https://angular.jp/guide/ngmodules)です。今回はやりません。また別の機会に回します。
最後に、Pythonのinspectや特殊メソッドが面白いです。終わり。