Python — DI (dependency injection ) — Part 2 — Containers

Snir Orlanczyk
2 min readNov 29, 2023

In the previous article I covered the use of manual DI injections with lambda defaults, but it only covers very simple use cases and forces the inject class or the consuming class to import the dependency, which beats the purpose of DIP where we depend on an abstraction rather than the concrete class.

What is a DI container?

DI container is a type of implementation of the DI pattern, It manages the instantiation and lifetime of objects (dependencies) in an application and injects them into other objects that require them.

# class_a.py
from di import container
from typing import Protocol


class iClassA(Protocol):
def do_someting():
pass


class ClassA(iClassA):
def do_someting():
print("something")


container.register(iClassA, ClassA())
# class_b.py
from di import container
from .class_a import iClassA

class ClassB:
def __init(self):
self.a = container.resolve(iClassA)
self.a.do_something()

This example illustrates the power of using a DI container, it makes using dependencies easy as we don’t need to manage the dependancy’s life cycle, or be aware of its concrete implementation.

DI container frameworks

This repo contains a list of common DI frameworks for python.

Each DI container solution is slightly different, but they all work mostly the same and implement a service locator pattern, but with different bells and whistles attached to it.

Main features for DI containers

Most DI container will have the following basic features that you need to be aware of it and see if it fits your needs

  • Registry type — you should be able to register an object as memorized of factorized (singleton or transient).
  • auto-wire — allows you to resolve the dependencies in the init method without explicitly writing the resolving code (usually done with a decorator).
  • multiple containers — sometimes you will need to have multiple containers, but i found that usually it makes things more complicated and most things can be solved with a central container.

Abstracting the container

To try and avoid being locked to a specific framework you can try and wrap the use of your container with your on calls, to minimize/prevent breaking changes if you ever need to replace it.

In this example I created a wrapper around kink, i used it as a very simple DI container that covers all the basic needs, and the implementation of it is straight forward and simple to understand and review.

The abstraction creates a static container that can be used throughout the app in addition to a wrapper around the auto wire decorator and a bootstrap function as a single place to register the core dependancies in the app that should be loaded first (i.e logger).

Conclusion

Manual DI is a really good solution if you have a small low complexity app and you want to create loose coupling between your components.

A DI container is a great solution as well but it comes with higher adoption cost and more complexity, but provides more flexibility and features.

--

--

Snir Orlanczyk

iOS developer by day, iOS developer by night (one does not simply stop developing an app)