Inversion of Control — Python anti-pattern

Illia Pekelny
3 min readOct 27, 2018

In this story, I consider IoC in the form of Dependency Injection. If statically typed languages are your jam then you know what DI is in practice but probably never tried on dynamic typing. Otherwise, if most of your experience is in dynamically typed languages than you knew about DI mostly in theory like it was with me. Sooner or later you will try to implement DI IoC on dynamically typed language out of curiosity… I used DI on Go and it was natural, simple and produced very neat code design. So why not to do it on Python???

Research

When I was looking for DI pattern implementation on Python I found a topic on StackOverflow with the same question which I had. I found the answer of Serban Teodorescu very interesting.

Great! Don’t you think so?

Creativity

I’m a code style junky like most of you, beloved Pythonistas. So I had this question too. Why the hell I need an empty class? I have come to a meta-programming solution:

Same but no extra class. I was so happy and proud of the solution. I had heavily advertised it to my team and introduced the pattern to the whole new project.

Fail

I’m a VIM user and entrust. I use the Jedi plugin for code analysis. It works like most of the autocompletion tools — it applies static code analysis. And Jedi doesn’t understand the metaclass magic I implemented. I lost autocompletion, jump-to-definition, and list-usages features. Moreover, call stack became nontrivial cause it doesn’t look as code written anymore. That you will see in a call stack:

<functions of a non-existing class defined in the first class of an inheritance tree> calling <functions of the last class of the inheritance tree>.

[0] di.py(32)<module>()

-> ENTITY.entity_prop

[1]di.py(14)entity_prop()

-> return super(Entity, self)._lower_level_meth()

[2] > di.py(26)_lower_level_meth()

-> print(‘{}\n{}’.format(self.pub_attr, self.__private))

Damn! It is tricky! Do you want to see this ^^ in your pdb as a call stack??? No f*** way, especially when there are more than two classes in an inheritance tree!

When you made a typo, forgot to implement a method you can just guess what is wrong… for hours…

“Brain On” mode

Why do we need inversion of control?

  • Decouple business logic from implementation.
  • Isolate third-party dependencies from business logic.
  • Simplify maintenance.

What the problems of normal Python code when it comes to maintainability?

  • Implicit and uncontrolled types.
  • Lack of control over the implementation of required methods.

Dependency Injection is a great approach but being this way it doesn’t solve Python issues and decouples business logic in a too tricky way.

Pythonic way

This time I wanted to keep my code straightforward, solid and decoupled. I needed interfaces to properly decouple logic from its implementation. And here they are — abc module! So my Pythonic solution is high-level mixins containing business logic defined with abc.ABCMeta metaclass + its’ lower level implementations.

Benefits of this approach:

  • It is more explicit — not implemented method is defined in an abstract class.
  • It controls the implementation of an abstract type (works as an interface).
  • It uses normal inheritance. __mro__ properties of the objects are normal.
  • Autocompletion, jump-to-definition and list-usages features work well.
  • It decouples business logic from implementation.
  • It isolates dependencies.

Conclusion

Applying architectural approaches don’t try to follow blindly concepts that don’t fit the language. Rather think about what do you want to reach and what is good for your team.

Python implements IoC inside of the interpreter, Python is the IoC framework. It is not what you expect to see in Java or C#. And Python doesn’t need magic tricks to reach the goals of IoC.

--

--

Illia Pekelny

Self — the source of sense centralized in time and space.