Pythonic Dependency Injection

with examples

Guilherme Latrova
7 min readDec 6, 2020

Before we dive into it, let’s review some acronyms and what they represent:

  • DIP (Dependency Inversion Principle) is a principle.
  • IOC (Inversion Of Control) is a principle.
  • DI (Dependency Injection) is a pattern.
  • IOC Container is a framework.

Introduction

The first thing that I thought that was missing when I came from a C# background to Python was:

What should I use for Dependency Injection? How can I create interfaces?

And looks like that’s a pretty common question from anyone coming from a typed language background, based on these stack overflow questions:

Please, note how all these questions always mentions: “Well, in Java/C#/other language…”. Let me interrupt it right there because, well, you’re not in Java, C# or anything else, so how should you face this problem?

🤔 What’s Dependency Inversion Principle and why should I care about?

Let me make it simple, inversion of control states that your code should respect two rules:

  • Modules should not depend on implementations, but abstractions instead;
  • Implementations should depend on abstractions;
DIP concept
DIP concept

In other words, you can achieve that in C# with something like:

Ok, note how Module knows nothing about Implementation, and how Implementation knows nothing about Module - Instead, both of them know only IDependency which can be seen as a contract between them that says: "Hey, you should implement method work with no args and return void".

Cool, got it, why should I care? Well, it brings us a couple of benefits:

  • You can replace anytime Implementation with other variations of code (Think about an INotification that has WhatsAppNotification and SMSNotification so you can switch between them anytime)
  • Your code can be tested isolated, since you can mock an IDependency

This makes your code insanely scalable, you should write code like that always.

(Honestly, I can’t imagine a high quality C#/Java code that does not respect this rule.)

⛓️ Python does not have interfaces

That’s damn trick, how am I suppose to depend on abstractions if Python doesn’t support interfaces?

Well, there’s the ABC module that would allow you to write an abstract class disguised as an interface. But honestly, I don't believe Python need interfaces.

That’s somewhat shocking, after researching a lot on StackOverflow and other posts, I came up to a conclusion:

Interface is a solution for languages that does not support multiple-inheritance.

And guess what? Python supports it (I won’t talk about the diamond problem caused by such decision).

Languages that allow only single inheritance, where a class can only derive from one base class, do not have the diamond problem. The reason for this is that such languages have at most one implementation of any method at any level in the inheritance chain regardless of the repetition or placement of methods. Typically these languages allow classes to implement multiple protocols, called interfaces in Java. These protocols define methods but do not provide concrete implementations. This strategy has been used by ActionScript, C#, D, Java, Nemerle, Object Pascal, Objective-C, Smalltalk, Swift and PHP.[13] All these languages allow classes to implement multiple protocols.

From Wikipedia.

NOTE: Abstract classes

An Abstract Class is a way of sharing behaviors and implementations between its subclasses and also enforcing some contract between subclasses. You should use it if required, my advice is not against abstract classes, but “pure” disguised abstract classes as interfaces.

Ok, but how can I enforce a contract, then? How can I guarantee that my implementation has all required methods and details?

Well, that brings us to the next topic, which is:

🦆 Python is dynamically typed

Man, have you realized that you need to specify types in some languages, but not in python? (type hinting does not enforce it btw).

Look at the following python function:

This function does not care about what passes, it will return the “sum” of 2 arguments.

This means that you can use it like:

Such thing is called duck typing, a concept that says: if it quacks like a duck then it must be a duck.

In other words, your code assumes that having the methods/functions that you need means your implementation is fine.

That’s very powerful. But with great powers comes great responsibility. So I’ll repeat the question:

How can I enforce a contract? How can I rest assured that my code has implemented everything needed, and for God Sake it’s taking args and returning correctly?

Well, you can’t. At least, not with regular classes.

How to solve that problem then? Well, I would ask you, regardless of how you enforce things, how would you prove me that your code implementation is correct and works for real?

🧪 Unit Testing is the key

The same way you SHOULD test that a function returns the expected result, you should test that your implementation works fine in your code.

And that’s it, you don’t need an interpreter to force you to do stuff, you’re grown up, you can ensure you did it yourself.

Let me try to prove my point, because talk is cheap.

Let’s consider we have a CronJob that runs every hour and we want to be notified somehow whenever it gets executed.

Note that we’re receiving a “notificator” and it should contain a send method - and that's it.

On C# you would implement it as:

Now in python, let’s imagine we have 3 implementations with 1 not respecting our send method.

How can we ensure we’re not sending this broken TelegramNotification into production and ruining the whole thing?

We can have these unit tests to ensure everything works:

I decided to split the working suite from the failing one to make it easier to spot.

Note what’s really interesting

  • Mocking does not “fix” a broken contract
  • No interfaces have been written, we still proved that the implementation works

But hey, don’t trust me, please go straight to the repository, clone it execute and see it by yourself on GitHub

I’m adding to the source code a scenario where I believe it would be fine to implement abstract classes as well.

🐍 Can we implement it on Python?

Yes! but that may depend on how you see things though.

Again, Inversion of Control Principle states that:

  • Modules must depends on abstractions;
  • Implementations must depends on abstractions;

and that’s how I honestly see it now:

DIP languages
DIP languages

A python dynamic type that can be anything is the abstraction.

So, based on our examples, that’s how we can see it:

DIP example
DIP example

notificator (the argument inside CronJob), is an abstraction and WhatsAppNotification is the actual implementation.

But please note, CronJob receives an implementation through the abstraction, that's why we're respecting the principle.

Examples

Abstract classes

I have absolutely nothing against abstract classes, I use them quite often by the way. I’m just opposed to writing more lines of code that don’t add value. If you’re writing unit tests (I really hope you are) why to enforce the same thing twice?

If you have an actual behavior that you want to share, then it starts making sense to me. You need to ensure a base class has something to share, without deciding details of the implementation.

(See this example for a valid reason on using abstract classes: GitHub).

Dependency Injection on classes

As the example showed above, it does respect the principle:

By allowing our CronJob to receive anything that's perfectly ok. You might also provide a default, so you can do it like:

If you allow your code to take ANY implementation, then IMHO it still respects this principle.

Dependency Injection on modules

Different from other languages python has “modules” that can contain several functions.

In such scenario there’s no constructor to inject the implementation.

So I often end up doing:

See? notificator is now a module dependency, and it can be anything thanks to Duck Typing. We need to implement unit tests to ensure that our notificator respects the send contract.

KISP: Keep It Simple, Pythonista — no IoC Containers

Again, if you come from other languages, you probably are familiar with a few IoC Containers.

I can’t see a good reason to implement them in Python. Your code gets verbose, new developers don’t really understand what’s happening behind your code (honestly neither do I lol).

Why to add more lines to your code if you can achieve:

  • Loosely coupled code
  • Highly testable code

without an IoC framework?

⭐ Conclusion

Let’s wrap it up:

  • Respect and honor DIP to achieve high quality decoupled code;
  • Python does not have interfaces, you should write unit tests to ensure contracts are respected;
  • Abstract classes are welcome when you want to share some behavior;
  • You don’t need IoC containers;

Do you disagree? Please reply and point out my weak spots, so we can learn more together 😃. Example code is encouraged and can enrich the experience.

Originally published at https://blog.guilatrova.dev/pythonic-dependency-injection/ .

🔖 References

I relied on a few links to write this article:

--

--

Guilherme Latrova

TechLead and Software engineer passionate about making things happen ❤