Even though the usage of Dependency Injection is not as common in the Python community as it is in the C# or Java communities, it is still a very powerful way to implement the Dependency Inversion Principle. Thankfully there are several packages available to us that provide us with a dependency injection implementation, which I will discuss in this article.
Dependency injection is a style of object configuration in which an objects fields and collaborators are set by an external entity. In other words objects are configured by some other object. When you are using Dependency injection an object is no longer responsible for configuring itself. This is taken care of by the container instead. This might be a bit abstract so let's start with a simple example:
import imaplib class EmailClient: def receive(self, username: str, password: str) -> List[str]: server = imaplib.IMAP4('localhost', 993) server.login(username, password) server.select('INBOX') result, data = server.uid('search', None) # Process result and data
In this example we have an
EmailClient that uses
imaplib to receive email messages.
The problem we have here is the fact we hardwired
imaplib.IMAP4 into the email client
so we cannot use another protocol like
We can solve this with dependency injection. For this we make use of Pythons duck typing. First we create a Protocols we can use to define what we expect.
from typing_extensions import Protocol class EmailReceiver(Protocol): def login(self, user: str, password: str): ... def select(self, mailbox='INBOX', readonly=False): ... def uid(self, command, *args): ...
Next we change the EmailClient so it takes a
ReceivingEmailProtocol and uses that to connect
class EmailClient: def __init__(self, email_receiver: EmailReceiver): self.server = email_receiver def receive(self, username: str, password: str) -> List[str]: self.server.login(username, password) self.server.select('INBOX') result, data = self.server.uid('search', None) # Process result and data
Lastly we create a new instance for the email receiver and start receiving email
receiver: EmailReceiver = imaplib.IMAP4_SSL("localhost", 993) client = EmailClient(receiver) results = client.receive("firstname.lastname@example.org", "mysupersecretpasswd")
As you can see, in this case we are using the
IMAP4_SSL instead of just
without having to change the
If you've been researching Dependency Injection frameworks for python, you've no doubt come across this opinion:
You dont need Dependency Injection in python. You can just use duck typing and monkey patching!
The position behind this statement is often that you only need Dependency Injection in statically typed languages.
To be honest, you don't really need Dependency Injection in any language, whether it is statically typed or not. Dependency Injection can make you life a lot easier though when building large applications. In my experience monkey patching should be kept to a minimum. I only use it in my tests, for example when I create mocks.
This chapter compares dependency injection frameworks for Python. I have limited my research to dependency injection frameworks that are still actively maintained and have a decent number of users. I also did not include any examples because each framework already provides very good examples.
Dependency Injector is a dependency injection microframework for Python created by ETS Labs. It was designed to be a unified and developer-friendly tool that helps implement a dependency injection design pattern in a formal, pretty, and Pythonic way.
Pinject is a dependency injection container for Python created by Google. It's primary goal is to help developers assemble objects into graphs in an easy, maintainable way.
Python dependency injection framework, inspired by Guice which aims for simplicity, doesn't use a global state and uses static type checking.
Dependency injection the python way, the good way. Not a port of Guice or Spring.