Dependency injection: pass collaborators in, don't create them inside
Dependency injection (DI) is a technique where a component receives its dependencies from outside rather than creating them internally. In Python, this often means passing dependencies as arguments to init or to the function that needs them. It improves testability because you can inject fakes or mocks in tests, and it reduces coupling because the component does not depend on a concrete implementation. It also makes dependencies explicit, which improves readability. There are frameworks such as dependency-injector or FastAPI's Depends, but manual DI is often sufficient and more transparent. The trade-off is more wiring code, but this pays off in testability, flexibility, and the ability to swap implementations without changing the component.
Constructor injection: pass dependencies to init. Most common and explicit.
Method injection: pass dependencies to the method that needs them. Useful for one-off collaborators.
Parameter injection: pass simple values or callables as arguments.
Frameworks: FastAPI's Depends, dependency-injector, or manual wiring in a composition root.
Trade-off: explicit wiring versus convenience of global imports. DI makes dependencies visible and testable.
Common mistake: using a service locator or global registry, which hides dependencies and makes testing harder.
Common mistake: injecting too many dependencies, which indicates a class with too many responsibilities.
Version note: DI is a pattern, not a library. Python's dynamic nature makes manual DI straightforward.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience