
Dependency Injection is a main stay in static languages, but does it make sense to use it in Python? Will it make me a better Python programmer?
And, wait, what is dependency injection, again?
First, let’s answer the question: What is dependency injection (DI)?
Dependency injection is a pattern that, when used, moves the construction of an object outside of the class. Instead, the class takes a dependency on an abstraction.
In static languages like C# and Java, dependency injection is heavily used to reduce coupling and facilitate testing. In fact, you’ll find dependency injection frameworks whose sole purpose is to maintain dependencies and their relationships to each other.
Dependency injection serves two main purposes:
First, it reduces complexity by taking a dependency on an abstraction.
Second, depending on an abstraction allows different implementations, including mocks for testing, to be passed into the class or function.
Let me demonstrate with some code:
# Before
class User:
def __init__(self):
self.database = SqlServerDatabase()
def get_details(self, user_id: int):
self.database.get_user_details(user_id)
# After
class User:
def __init__(self, database: Database):
self.database = database
def get_details(self, user_id: int):
self.database.get_user_details(user_id)
This is dependency injection in its simplest form. While the concept is straightforward, its power lies in enabling flexible designs.
In the Before
example, the User
class is tightly coupled to the SqlServerDatabase
class. If we want to test the User
class, we need to create a new SqlServerDatabase
instance.
In the After
example, the User
class is loosely coupled to the Database
abstraction. We can pass in a different implementation of the Database
abstraction to the User
class.
Let me demonstrate this flexibility with a practical example that shows how we can switch between different database implementations:
date_string = "2023-10-01" # Example date string
date_format = "%Y-%m-%d" # Input string format
birthday = datetime.strptime(date_string, date_format)
turn_of_the_century = datetime.strptime('2000-01-01', date_format)
database = PostgresDatabase("")
if birthday < turn_of_the_century:
database = SqlServerDatabase("")
user = User(database=database)
user.get_details(user_id=1)
On line 6 (birthday < turn_of_the_century
), dependency injection allows us to swap implementations easily based on the different conditions. While this flexibility is valuable for production code, one of the most common uses for dependency injection, especially in static languages, is in testing.
Here’s an example:
class UserTests(unittest.TestCase):
def test_is_authenticated(self):
database = MockDatabase('connection_string')
is_authenticated = User(database).is_authenticated('user', 'pass')
self.assertTrue(is_authenticated)
This is a simple example using a MockDatabase
class. In Python, we could also use the built-in Mock
class to achieve the same outcome.
Interestingly, dependency injection wasn’t widely used in the Python projects I’ve worked on. Coming from a static language background, I was surprised— and it seemed counterintuitive.
However, there’s a reason for this limited adoption. Python’s built-in patch functionality already provides excellent testing capabilities, eliminating one of dependency injection’s main benefits. While Dependency Injection can still help reduce complexity, Python has other approaches to achieve the same goal.
I’m not saying that Dependency Injection shouldn’t be used in Python. Quite the contrary, like all tools and patterns, there is a place and time to use them. And Dependency Injection is just another tool in your tool belt that will improve the quality of your code.
I think Dependency Injection would generally increase the code quality in most Python projects.
If you’re interested in exploring Dependency Injection further, I recommend checking out two popular Python frameworks:
- Injector (github.com/python-injector/injector)
- Dependency Injector (python-dependency-injector.ets-labs.org)
Author: Chuck Conway specializes in software engineering and Generative AI. Connect with him on social media: X(@chuckconway) or visit him on YouTube.