DI Container is a system that automatically passes an instance of the appropriate type to the argument of the constructor when creating an instance of a class. It makes it easier to implement DI patterns and expresses loosely coupled code.
If you want to know more about DI patterns, please refer to another article. Actually, it's very easy to make, so I'll do my best.
I will link the completed repository. Repository
DEMO
Register in the DI class that this is the implementation class, or that this class will be used in a singleton. After that, the flow is to ask the DI class to create an instance of this class.
Look at the squid DEMO.
class Animal:
def __init__(self, kind: str, name: str):
self.kind = kind
self.name = name
def __str__(self):
return "I am {kind}. My name is {name}".format(kind=self.kind, name=self.name)
class Cat(Animal):
def __init__(self, name: str = "Cathy"):
super().__init__('Cat', name)
di = DI()
di.register(Animal, Cat)
print(di.resolve(Animal))
# > I am Cat. My name is Cathy.
class Person:
def __init__(self, name: str, role: str):
self.name = name
self.role = role
def __str__(self):
return "I am {name}. My role is {role}.".format(name=self.name, role=self.role)
nukumizu = Person('Nukumizu', 'Actor')
di.register_singleton(Person, nukumizu)
print(di.resolve(Person))
# > I am Nukumizu. My role is Actor.
The minimum requirements for a DI Container are the following three.
--Container class for registering the corresponding class or singleton --Resolver class that generates registered classes, etc. --DI class of the above two façade classes
Let's take a look at the actual code in order.
Container Let's start with the Container class. This class provides the following functionality:
--Ability to register implementation class for base class --Ability to register singleton instances --Ability to get implementation class from base class --Ability to get registered singleton instances --Ability to check if it is registered --Ability to clear registered data
Let's take a look at the actual code.
container.py
class DIContainer:
concrete_table = {}
singleton_table = {}
def register(self, base_cls: type, concrete_cls: type):
if not issubclass(concrete_cls, base_cls):
raise TypeError('Concrete class is required {} not {}.'.format(base_cls, concrete_cls))
self.concrete_table[base_cls] = concrete_cls
def register_singleton(self, t: type, instance: object):
if not isinstance(instance, t):
raise TypeError('Instance type is required {} not {}.'.format(t, type(instance)))
self.singleton_table[t] = instance
def get(self, t: type):
if self.is_registered_concrete(t):
return self.concrete_table[t]
return t
def get_singleton(self, t: type):
if self.is_registered_singleton(t):
return self.singleton_table[t]
raise KeyError('{} is not registered as singleton.'.format(t))
def is_registered_singleton(self, t: type):
return t in self.singleton_table.keys()
def is_registered_concrete(self, t: type):
return t in self.concrete_table.keys()
def is_registered(self, t: type):
return self.is_registered_concrete(t) or self.is_registered_singleton(t)
def clear(self):
self.concrete_table.clear()
self.singleton_table.clear()
Since I only register honestly, I think there is no place to explain in particular. I'm sorry if the code is hard to read.
Resolver Next, let's look at the Resolver class. This class provides the following functionality:
--Ability to generate registered classes --Ability to instantiate class init arguments
There are only two.
Let's take a look at the actual code.
resolver.py
from container import DIContainer
class Resolver:
def __init__(self, container: DIContainer):
self.container = container
def resolve(self, cls: type):
if self.container.is_registered_singleton(cls):
return self.container.get_singleton(cls)
cls = self.container.get(cls)
init_args = self.resolve_init_args(cls)
return cls(**init_args)
def resolve_init_args(self, cls: type):
init_args_annotations = get_init_args_annotations(cls)
defaults = get_init_default_values(cls)
result = {}
args_count = len(init_args_annotations)
for key, t in init_args_annotations.items():
if self.container.is_registered(t) or len(defaults) < args_count:
result[key] = self.resolve(t)
else:
result[key] = defaults[len(defaults) - args_count]
args_count -= 1
return result
def get_init_args_annotations(cls: type):
if hasattr(cls.__init__, '__annotations__'):
return cls.__init__.__annotations__
return {}
def get_init_default_values(cls: type):
if hasattr(cls.__init__, '__defaults__'):
result = cls.__init__.__defaults__
return [] if result is None else result
return []
I will explain get_init_args_annotations outside the class. All you need to create a DIContainer is the constructor argument information. It won't start without this. The way to get the constructor information in python is annotations. Now you can get an associative array with the argument name Key and the class type Value. An example is shown below.
class Person:
def __init__(self, name: str, age: int):
pass
print(Person.__init__.__annotaions__)
# {'name': <class 'str'> , 'age': <class 'int'>}
Create an instance of the class based on the information obtained by \ __ annotations__.
Now let's check the flow around the generation process.
It feels like recursively creating classes. Now that you have all the functions as a DI Container, you can use it by creating a façade.
di.py
from container import DIContainer
from resolver import Resolver
class DI:
def __init__(self):
self.container = DIContainer()
self.resolver = Resolver(self.container)
def register_singleton(self, t: type, instance: object):
self.container.register_singleton(t, instance)
def register(self, base_cls: type, concrete_cls: type):
self.container.register(base_cls, concrete_cls)
def resolve(self, t: type):
return self.resolver.resolve(t)
def clear(self):
self.container.clear()
At this point, you can use DI Container like DEMO.
Once you understand it, you will be less likely to forget how to use it. Knowing the DI pattern, I solved some questions about handling Interface relationships.
By introducing DI Container to my library at my previous workplace, it was extremely convenient not to have to check how to generate classes one by one. Of course, there are disadvantages, but it is still convenient, so I will use it when it can be used.
I will link the completed repository. Repository
I would appreciate it if you could let me know if there are any deficiencies.
Thank you for staying with us until the end.
Recommended Posts