5 ms·
> Minimalist dependency injection in Python When an object requires other objects for its work, pass references to those other objects into its __init__ method
by pansa2 5y ago
> Minimalist dependency injection in Python
When an object requires other objects for its work, pass references to those other objects into its __init__ method. That’s it.
- rmbyrro 5y agoYou clearly haven't read the article
- programmarchy 5y agoYo dawg, I heard you like dependencies, so we created a dependency injector dependency for you to inject so you can inject your dependencies. Sorry couldn't resist. The additional dependency is the biggest problem I have with OP's approach though. Now none of my classes are easily portable across projects, and this DI framework is going to spread around everywhere.
- ldlework 5y agoAll you've done is push the burden of constructing the dependency to whomever is calling an passing that dependency to __init__ Consistently applied, all construction gets pushed to the entry-point of the program. Congratulations, you've just discovered the so-called "composition root". Now that all construction is taking place at once, the order matters as you can't pass a dependency to its dependent until the dependency has been constructed. But it may have its own dependencies. So now there is a topological sorting problem. Turns out computers are really good at topological sorting. So, someone made the computer do it, and we call that a dependency injection container. Tada.
- gonzo41 5y agoDI containers don't stop circular dependencies. It's not a massive advantage.
- mayank 5y agoYes, but modern DI (like Dagger for Java) can detect cyclic dependencies at compile time and break the build.
- gonzo41 5y agoI know, but just thinking though what you're doing and building a program for right now, rather than building for a future that may never happen is probably going to deliver more success. I like constructor based dependency injection personally. It's simple and I don't really see a stack of value obsessing about the possibility of code reuse.
- contravariant 5y agoI can detect a cyclic reference before I even finish writing the code, what's the point of letting the compiler figure it out?
- mayank 5y ago> I can detect a cyclic reference before I even finish writing the code, what's the point of letting the compiler figure it out? The compiler generally has better attention to detail and the ability to deal with larger object graphs than the typical human.
- contravariant 5y agoSure, but how are you going to write code that uses a class with a circular dependency?
- KronisLV 5y agoCuriously i've actually seen people use @Lazy in Spring projects to allow for circular dependencies, to deal with situations where the services or their dependencies aren't structured like a neat tree with leafs, but rather some interdependent graph with cycles. Honestly, in practice it worked and wasn't too bad to work with, which was interesting to behold - in practice everyone talks about how circular dependencies are bad for a variety or reasons (trying to print or process data and ending up with endless loops comes to mind), but then there just was that system that was chugging along without a care in the world.
- teh_klev 5y agoYes they can and do. The DI container in .NET core does this upfront and will thrown an exception if it detects circular references.
- choeger 5y agoIf you have so many top-level components or change your dependency graph that often that sorting becomes an issue you want to automate, you might want to reconsider your architecture.
- anothernewdude 5y agoAll you are doing is pushing the problem to runtime.
- Sankozi 5y agoTo any single integration test to be precise. So your builds fails few seconds later if something is wrong.
- valenterry 5y agoOf course the order matters - but you know what? It's good that this is explicit in the sourcecode and even checked by the compiler. Since now, I can go to the project's root and read step by step how the application is architected and build up and which are core dependencies and which are not. With dependency injection containers, everything is shuffled around and I'm now at the mercy of a library to tell me what I want to know.
- rallyaround 5y ago>you can't pass a dependency to its dependent until the dependency has been constructed. > But it may have its own dependencies. So now there is a topological sorting problem. I guess I just don't see the problem. That solves itself naturally through normal programming. Some function takes A as argument? Well I obviously make that A first. A requires B to setup? Well obviously I make that first. I don't need to "sort" anything, it just follows naturally from the types and constraints of the API.
- yen223 5y agoBest part is you haven't introduced new constructs or DSLs or XML configuration. It's just plain old Python functions calling other functions.
- garethrowlands 5y agoYes you're right, it works really really well.
- paskozdilar 5y ago> Consistently applied, all construction gets pushed to the entry-point of the program. Congratulations, you've just discovered the so-called "composition root". I never knew this has an explicit name. > Turns out computers are really good at topological sorting. So, someone made the computer do it, and we call that a dependency injection container. Tada. What exactly is a "dependency injection container"? I've searched the internet for the definition, but I've only gotten more confused by all the PHP and C#. You mention topological sorting of dependencies - does that assume a hierarchical object model or can it be used with basically anything? EDIT: From what I've read, it appears that a Dependency Injection Container is just an object that: 1) for each dependency type, has a `GetDependency: Type -> Object` method 2) lazily creates dependencies as required by the dependents and the dependency dependencies. Is this accurate?
- tored 5y agoAnd usually configurable, e.g. for interface Reader use FileReader implementation.
- ivanche 5y agoBeautiful article by Mark Seemann on composition root [1]. Another one on when to use DI container [2]. [1] https://blog.ploeh.dk/2011/07/28/CompositionRoot/ https://blog.ploeh.dk/2011/07/28/CompositionRoot/ [2] https://blog.ploeh.dk/2012/11/06/WhentouseaDIContainer/ https://blog.ploeh.dk/2012/11/06/WhentouseaDIContainer/
- SideburnsOfDoom 5y agoit is at heart quite simple. But this characteristic doesn't always mean that it has few benefits. Off the top of my head, things that DI enables include: * swapping out a class for an equivalent becomes an configuration concern. * whether a type is a singleton or transient becomes a separate concern to that class's implementation, and it can be changed in the app startup config code. There can be drawbacks too, if done badly, but I don't think that the argument that "this doesn't do very much" is entirely relevant to the pros and cons.
- necovek 5y agoWhile some configurability is desireable, being explicit is even more so. I wonder why Zope died out? It has everything, it's a completely sound design, and makes all components interchangeable, and it's very pretty when done properly. It died out because it's verbose and gets hairy quickly in the real world where not everyone is on the same page, so it never ends up being used "properly". Go for simpler and you have a higher chance of everybody getting the intent on their first reading of code. It's going to be hard to get people to not do DI "properly" if you simply ask them to pass dependencies in (though I am sure they still could by eg. passing classes vs instances in).
- bagels 5y agoSeems like the most minimalist. Maybe too minimalist though?
- zby 5y agoI really like that. Not just for testing - it makes the libraries much more flexible. But to make it a bit less burdensome for the most common usage patterns I like to relax it a bit with the possibility of building default object params in __init__.
- BerislavLopac 5y agoI usually provide two to three ways to inject dependencies, especially if an object needs multiple dependencies of the same type: 1. An add_foo instance method. 2. Arguments to the `__init__` method, ideally as *args, using add_foo internally. 3. Optionally, an @instance.foo decorator which inserts the dependency as it's being defined (again, using add_foo internally).
- iovrthoughtthis 5y agothis is the best way. everything else is incredibly over complicated and magic
- PartiallyTyped 5y agoWhat about builder patterns?
- necovek 5y agoI was going to propose the same thing with code: class MyDep: def do_foo(self): print("fooo!") class Fooser: def __init__(self, foo_dooer: MyDep): self.foo_dooer = foo_dooer def act(self): self.foo_dooer.do_foo() class FakeDep(MyDep): def do_foo(self): print("booo!") # With the grand unveiling: fooser = Fooser(MyDep()) Seems a lot simpler than 100 lines they brag about, yet this includes the business logic implementation for the code as well (dependency injection takes a couple of lines total; if you add interface handling or defaults, you'll add a few lines).