Constructor Injection in Python
Constructor injection supplies an object's dependencies as constructor arguments instead of having the object create them internally. It makes required collaborators explicit and lets callers provide production implementations or test doubles.
Constructor injection is a form of dependency injection: a caller supplies an object’s dependencies when it constructs the object. In Python, this commonly means accepting dependencies as parameters to `__init__` and storing them on `self`.
The class depends on an abstraction or collaborator, such as a repository, rather than constructing that collaborator internally. The caller chooses the concrete implementation and can provide a different one in another context.
Because required dependencies appear in the constructor signature, they are visible to callers and can be checked when the object is created. This also makes unit tests easier: a test can pass a small fake or stub instead of connecting to a real external system.
Use constructor injection for dependencies an object needs to do its work. Keep constructors focused; a long list of unrelated dependencies can indicate that the class has too many responsibilities or that its design needs reconsideration.
Key points
- Pass required collaborators into `__init__` rather than constructing them inside the class.
- Store injected dependencies on the instance when methods need to use them later.
- Injection makes dependencies visible and replaceable, which supports focused tests.
- Keep constructors manageable; many unrelated dependencies can signal excessive responsibilities.
Interview questions
What is constructor injection, and why is it useful?
Constructor injection means passing an object's dependencies to it when it is instantiated. The object stores and uses those collaborators instead of creating them internally. This makes required dependencies explicit and lets callers choose implementations. It also makes it straightforward to supply fakes in tests.
How does constructor injection help unit testing in a Python backend?
A constructor parameter exposes a required collaborator as part of the class's creation contract. A test can pass a fake repository that implements the operations the class calls, avoiding database setup and making outcomes predictable. The same class can receive a real repository in application wiring. Python annotations can document the expected interface, but they do not validate it at runtime by default.
Should a class create a default dependency inside its constructor?
I would normally inject required collaborators through `__init__`, because the object should not be usable in a partially configured state. A default instance created inside the constructor can conceal the dependency and make substitution harder. Optional behavior can be modeled explicitly when the dependency is genuinely optional, but a nullable parameter adds a case the class must handle. I would avoid mutable or expensive shared defaults and keep object creation in the composition layer.
Does constructor injection determine dependency lifetime?
Constructor injection provides dependencies; it does not by itself define their lifetime or scope. The application’s composition code decides whether to reuse an instance, create one per request, or create one per operation, according to the dependency's requirements. A shared mutable or request-specific dependency can cause correctness or concurrency problems if reused too broadly. Lifecycle management should therefore be explicit and separate from the receiving class's business logic.