Cursus
Dependency injection is een ontwerppatroon waarbij objecten of services (we bedoelen hier dependencies) van buitenaf aan een class worden geleverd, in plaats van dat de class ze intern aanmaakt. Dit ontkoppelt componenten.
Dependency injection in Python gebruiken heeft veel voordelen, zoals:
- Modulariteit: Je code wordt opgedeeld in kleinere, herbruikbare onderdelen.
- Testbaarheid: Het is makkelijker om je code te testen omdat je het echte onderdeel kunt vervangen door mocks.
- Onderhoudbaarheid: Je kunt delen van je code bijwerken of aanpassen zonder de rest van het systeem te breken.
In deze tutorial laat ik met eenvoudige, praktische voorbeelden zien hoe je dependency injection in Python gebruikt.
TL;DR
-
Dependency injection is een ontwerppatroon waarbij een class zijn dependencies van buitenaf ontvangt in plaats van ze zelf aan te maken. Het maakt je code modulairder.
-
Je kunt dependencies injecteren via de constructor, een settermethode of direct in een methode. Python ondersteunt dit handmatig of via frameworks zoals
dependency-injector,injectorenpunq. -
Populaire webframeworks gaan er verschillend mee om: FastAPI heeft ingebouwde ondersteuning via
Depends(). Flask en Django hebben een library zoalsdependency-injectornodig.
Dependency injection in Python begrijpen
Om te snappen hoe je dependency injection implementeert, is het belangrijk dat je de kernprincipes erachter begrijpt.
Dependency injection is gebaseerd op het principe Inversion of Control (IoC): in plaats van dat een class zijn dependencies aanmaakt en beheert, krijgt hij die van een externe bron. Dit helpt om je te focussen op één taak en houdt je code schoon.
Dependencies injecteren in Python kan op deze veelvoorkomende manieren, al zijn er ook andere:
- Constructorinjectie: Geef dependencies mee via de classconstructor.
- Setterinjectie: Stel dependencies in met een settermethode nadat het object is aangemaakt.
- Methode-injectie: Geef dependencies direct mee aan de methode die ze nodig heeft.
Waarom dependency injection in Python gebruiken?
Als developer kan het begrijpen en toepassen van dependency injection veel verschil maken in je codeontwerp. Laten we naar een paar voordelen kijken.
Modulariteit, herbruikbaarheid en flexibiliteit
Dependency injection helpt je om je code op te delen in kleinere, meer gefocuste componenten, wat meer flexibiliteit en modulariteit geeft.
Elk deel of component handelt een specifieke taak af en vertrouwt op externe dependencies, waardoor hergebruik in verschillende delen van jouw of andermans applicatie eenvoudiger wordt
Stel dat je een class hebt die berichten verstuurt. In plaats van e-mail- of sms-logica direct in je service te bouwen, kun je een e-mail- of sms-senderclass injecteren.
Je class die berichten verstuurt maakt zich dan niet druk om hoe het bericht wordt verstuurd, maar gebruikt simpelweg de meegegeven sender. Dit betekent dat je je berichtenzender met verschillende e-mail- of sms-senders kunt gebruiken.
Onderhoudbaarheid en testbaarheid
Met dependency injection kun je tijdens het testen echte dependencies eenvoudig vervangen door mockdata. Zo schrijf je makkelijker unittests zonder afhankelijk te zijn van API’s of databases.
Stel je als gedachtenoefening voor dat je een class hebt die data in een database opslaat. Voor testdoeleinden kun je een mockdatabase injecteren in plaats van een echte, zodat je test niet aan de productiedatabase gekoppeld is.
Dependency injection maakt het mogelijk om specifieke delen van je code aan te passen zonder het hele systeem te breken. Dit is vooral handig als je in een applicatie van betaalprovider moet wisselen, zoals van Stripe naar PayPal, zonder de rest van je code aan te passen.
Inversion of Control en uitwisselbare dependencies
Het idee achter inversion of control is dit: dependency injection verschuift de verantwoordelijkheid voor het aanmaken en beheren van dependencies naar een externe bron.
Dit betekent dat je eenvoudig de ene implementatie kunt inwisselen voor een andere; bijvoorbeeld als je een ReportGenerator-class hebt die rapporten opmaakt en exporteert.
In plaats van dat de class beslist in welk formaat er geëxporteerd wordt, kun je er een exporterclass in injecteren, zoals PDFExporter, ExcelExporter of CSVExporter.
De ReportGenerator-class maakt zich niet druk om hoe de export gebeurt; om van formaat te wisselen, vervang je simpelweg de geïnjecteerde exporter, zonder de ReportGenerator zelf te wijzigen.
Dependency injection in Python implementeren
Nu je een duidelijk beeld hebt van wat dependency injection is en waarom het nuttig is, gaan we kijken hoe je het in Python implementeert. We beginnen met de eenvoudigste methode: handmatige dependency injection.
Handmatige dependency injection
Bij handmatige dependency injection maak je dependencies zelf aan en geef je ze door aan de class of functie die ze nodig heeft.
Deze methode werkt goed voor kleine applicaties, maar naarmate je applicatie groeit, heb je een dependency-injectionframework nodig om fouten te voorkomen.
Hier is een voorbeeld van code zonder dependency injection
class EmailService:
def send_email(self, message):
print(f"Sending email: {message}")
class UserNotifier:
def __init__(self):
self.email_service = EmailService() # Creates its own dependency
def notify(self, message):
self.email_service.send_email(message)
notifier = UserNotifier()
notifier.notify("Welcome!")
In het bovenstaande voorbeeld is UserNotifier sterk gekoppeld aan EmailService. Je kunt EmailService niet eenvoudig vervangen of mocken voor tests.
Hier is een andere versie, maar met handmatige dependency injection.
class EmailService:
def send_email(self, message):
print(f"Sending email: {message}")
class UserNotifier:
def __init__(self, email_service):
self.email_service = email_service # Dependency is injected
def notify(self, message):
self.email_service.send_email(message)
email_service = EmailService()
notifier = UserNotifier(email_service)
notifier.notify("Welcome!")
De versie hierboven is flexibel en stelt je in staat om verschillende berichtendiensten te injecteren; je kunt EmailService ook vervangen door een mock in tests.
Het dependency-injector-framework gebruiken
Naarmate je applicatie groeit, wordt het complex en rommelig om dependencies te beheren.
Het dependency-injector framework biedt een gestructureerde aanpak.
Container-providerarchitectuur
Het dependency-injector-framework werkt op basis van een container-providerarchitectuur.
- Container: Dit is het centrale register voor de dependencies van je applicatie.
- Providers: Dit bepaalt hoe je dependencies worden aangemaakt.
- Configuratie: Hiermee kun je instellingen aanpassen en in objecten injecteren.
- Overriding: Hiermee kun je dependencies vervangen zonder applicatiecode te wijzigen, vooral tijdens tests.
from dependency_injector import containers, providers
# Services
class EmailService:
def send_email(self, message):
print(f"Sending email: {message}")
class UserNotifier:
def __init__(self, email_service):
self.email_service = email_service
def notify(self, message):
self.email_service.send_email(message)
# Container
class Container(containers.DeclarativeContainer):
email_service = providers.Singleton(EmailService)
user_notifier = providers.Factory(UserNotifier, email_service=email_service)
# Usage
container = Container()
notifier = container.user_notifier()
notifier.notify("Hello!")
In het bovenstaande voorbeeld:
-
Containerbepaalt hoe instanties worden aangemaakt -
Singletonzorgt ervoor dat er maar één instantie vanEmailServicebestaat -
Factorymaakt een nieuweUserNotifiermet deEmailServiceautomatisch geïnjecteerd.
Wiringmechanisme
Je kunt decorators zoals @inject gebruiken om de noodzaak van handmatige dependencydoorgifte te verminderen.
Hier is een andere versie van het vorige voorbeeld:
from dependency_injector import containers, providers
# Define services
class EmailService:
def send_email(self, message):
print(f"Sending email: {message}")
class UserNotifier:
def __init__(self, email_service: EmailService):
self.email_service = email_service
def notify(self, message):
self.email_service.send_email(message)
# Create container
class Container(containers.DeclarativeContainer):
email_service = providers.Singleton(EmailService)
user_notifier = providers.Factory(UserNotifier, email_service=email_service)
from dependency_injector.wiring import inject, Provide
@inject
def main(notifier: UserNotifier = Provide[Container.user_notifier]):
notifier.notify("Hello!")
if __name__ == "__main__":
container = Container()
container.wire(modules=[__name__]) # This wires the current module
main()
In de huidige versie:
-
Zonder de dependency handmatig toe te voegen injecteert de
@inject-decorator automatisch de functie- of classdependency. -
De
Provide-class verteltdependency-injectorwelke dependency moet worden geïnjecteerd.
Deze aanpak haalt de noodzaak weg om dependencies handmatig te wringen vanuit de container.
Dit is handig in grotere applicaties waarin services van meerdere componenten kunnen afhangen (vandaar de behoefte om ze automatisch te initialiseren).
Dependency injection gebruiken in populaire Pythonframeworks
De meeste moderne Pythonframeworks maken het makkelijk om met dependency injection te werken, zodat je schonere en beter testbare code kunt schrijven.
In dit deel leer je hoe dependency injection wordt toegepast in Flask, Django en FastAPI.
Dependency injection in Flask
Flask heeft geen ingebouwde ondersteuning voor dependency injection, maar je kunt het implementeren met de dependency-injector-library via de volgende stappen.
Stap 1: Definieer je services
class GreetingService:
def get_greeting(self, name):
return f"Hello, {name}!"
Stap 2: Richt je dependency-injectioncontainer in
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
greeting_service = providers.Factory(GreetingService)
Stap 3: Maak de Flaskapplicatie en injecteer dependencies.
from flask import Flask, request
from dependency_injector.wiring import inject, Provide
container = Container()
app = Flask(__name__)
@app.route("/greet")
@inject
def greet(greeting_service: GreetingService = Provide[Container.greeting_service]):
name = request.args.get("name", "World")
return greeting_service.get_greeting(name)
container.wire(modules=[__name__])
if __name__ == "__main__":
app.run(debug=True)
Uit de code hierboven:
-
De functie
name = request.args.get(“name”, “World”)haalt denameuit de querystring en gebruikt standaard”World”als die niet is meegegeven. -
return greeting_service.get_greeting(name)roept de methode aan op de geïnjecteerde service en retourneert een begroetingsbericht. -
container.wire(modules=[__name__])vertelt de dependency injector om deze module te scannen op@inject-decorators enProvide[...]-markeringen om ze aan de daadwerkelijke providers te koppelen.
Stap 4: Test de applicatie
Voer de applicatie uit in je terminal met het commando python <name_of_file>.py en ga naar de URL http://localhost:5000/greet?name=Jacob.
Je zou het volgende moeten zien.

Dependency injection in Django
Net als Flask biedt Django geen ingebouwde dependency injection. Maar je kunt zijn class-based views, middleware en app-structuur gebruiken om dependency injection handmatig te integreren of met de dependency-injector-library.
Hier is een stapsgewijze procedure om dependency injection in Django te implementeren.
Stap 1: Definieer je service
Maak een eenvoudige service met je businesslogica in een nieuw bestand services.py in je appmap.
# myapp/services.py
class GreetingService:
def get_greeting(self, name):
return f"Hello, {name}!"
Stap 2: Richt de dependency-injectorcontainer in
Maak binnen je applicatiemap een container met de dependency-injector-library in een nieuw bestand containers.py.
# myapp/containers.py
from dependency_injector import containers, providers
from .services import GreetingService
class Container(containers.DeclarativeContainer):
greeting_service = providers.Factory(GreetingService)
Stap 3: Maak een view met geïnjecteerde dependencies
Gebruik de @inject-decorator in je view om dependencies te ontvangen in views.py.
# myapp/views.py
from django.http import HttpResponse
from dependency_injector.wiring import inject, Provide
from .containers import Container
from .services import GreetingService
@inject
def greet_view(
request, greeting_service: GreetingService = Provide[Container.greeting_service]
):
name = request.GET.get("name", "World")
message = greeting_service.get_greeting(name)
return HttpResponse(message)
Stap 4: Werk urls.py bij
Ga in je projectmap naar het bestand urls.py en koppel je dependency injector aan Django’s URL-routingsysteem.
from django.contrib import admin
from django.urls import path
from myapp.views import greet_view
urlpatterns = [
path("admin/", admin.site.urls),
path("greet/", greet_view),
]
Stap 5: Wire de container in apps.py
Ga naar apps.py en wire de container aan de applicatie.
# myapp/apps.py
from django.apps import AppConfig
from .containers import Container
class MyAppConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "myapp"
def ready(self):
container = Container()
container.wire(modules=["myapp.views"])
Ga vervolgens naar je appconfig in INSTALLED_APPS en voeg je applicatie toe.
# settings.py
INSTALLED_APPS = [
'myapp.apps.MyAppConfig',
...
]
Stap 6: Test de view
Start je Djangoserver en open de view via de URL http://localhost:8000/greet/?name=Django.
Je zou het volgende moeten zien.

Dependency injection in FastAPI
FastAPI heeft ingebouwde ondersteuning voor dependency injection via de speciale marker Depends() die injecteert wat een functie retourneert.
Hier is een stapsgewijze gids voor dependency injection in FastAPI.
Stap 1: Definieer een service
class GreetingService:
def get_greeting(self, name: str) -> str:
return f"Hello, {name}!"
Stap 2: Maak de dependencyfunctie
def get_greeting_service():
return GreetingService()
Stap 3: Richt de FastAPI-app in en gebruik dependency injection
app = FastAPI()
@app.get("/greet")
def greet(
name: str = "World", service: GreetingService = Depends(get_greeting_service)
):
return {"message": service.get_greeting(name)}
Stap 4: Run en test de applicatie
Start je server met het commando uvicorn main:app –reload en bezoek de URL http://localhost:8000/greet?name=FastAPI. Je zou de volgende response moeten zien.

Python dependency-injectorframeworks vergelijken
Er zijn verschillende Pythondependencyframeworks, elk met eigen sterke punten, syntaxis en use-cases. De juiste keuze hangt af van de grootte en structuur van je project.
Hier is een vergelijking van enkele gangbare Pythondependency-injectionframeworks.
|
Framework |
Het beste voor |
Sterke punten |
Beperkingen |
|
|
Complexe applicaties in productie |
Volledig uitgerust, snel en configureerbaar |
Langdradig |
|
Eenvoudig en geschikt voor minimale applicaties |
Schone syntaxis |
Minder geavanceerd dan anderen. |
|
|
Geavanceerde bindinglogica |
Auto-wiring |
Langzamer door introspectie |
|
|
Eenvoudige applicaties |
Minimaal en snel |
Beperkte features vergeleken met anderen |
|
|
FastAPI-applicaties |
Ingebouwde ondersteuning voor async en ook testbaar |
Niet herbruikbaar buiten FastAPI |
|
|
Flaskapplicaties |
Eenvoudige integratie met Flaskapplicaties |
Afhankelijk van |
|
|
Djangoprojecten |
Biedt dependency injection voor views en middleware |
Afhankelijk van |
Geavanceerde implementatiepatronen
Naarmate de codebase van je applicatie groter wordt, wordt dependency injection lastiger te onderhouden.
Je kunt geavanceerde dependency injection gebruiken voor taken als request-scoped resources, cleanup en separation of concerns.
Scoped dependencies
Scoped dependencies stellen je in staat te bepalen hoelang een dependency blijft bestaan, bijvoorbeeld wanneer je een nieuw object per request wilt, een gedeeld object voor de levensduur van de app of automatische cleanup van resources zoals databaseverbindingen.
De meeste dependency-injectorframeworks ondersteunen scopes zoals:
- Singleton: Eén instantie voor de hele app.
- Factory: Een nieuwe instantie elke keer dat hij wordt geïnjecteerd.
- Thread-local of request: Eén instantie per thread/request.
- Resource: Beheerd met setup- en teardownlogica.
Hieronder een voorbeeld van scoped dependencies met geneste containers met dependency-injector.
from dependency_injector import containers, providers, resources
# A simple resource that needs setup and cleanup
class MyResource:
def __enter__(self):
print("Setting up the resource")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Cleaning up the resource")
# Create a container to manage the resource
class MyContainer(containers.DeclarativeContainer):
resource = providers.Resource(MyResource)
# Using the resource with a context manager
container = MyContainer()
with container.resource() as r:
print("Using the resource")
In het voorbeeld hierboven bootst MyResource een class na die setup en teardown vereist, zoals het openen/sluiten van een bestand of database.
De container levert de resource met providers.Resource, en met with container.resource() wordt de context correct betreden en verlaten.
Dit patroon zorgt ervoor dat resources, zoals filehandles, databaseverbindingen of netwerksessies, netjes worden beheerd.
Stel je een API-endpoint voor dat een databasesessie nodig heeft voor het huidige request of een logger met requestspecifieke context.
Met scoped dependencies maakt het framework een request-scoped container aan wanneer het request start en worden alle dependencies uit die scope opgelost.
Wanneer het request eindigt, vindt eventuele noodzakelijke cleanup, zoals het sluiten van databasesessies, automatisch plaats. Dit zorgt ervoor dat state niet tussen requests gedeeld wordt en voorkomt resourcelekken.
Asynchrone injectie
Voor efficiënte afhandeling van I/O-gebonden taken heb je asynchrone uitvoering nodig. Dependency-injectionsystemen in frameworks zoals FastAPI en dependency-injector bieden asynchrone injectie met async/await.
Asynchrone injectie betekent het injecteren van dependencies die async def-functies zijn of het beheren van asynchrone resources zoals async databaseverbindingen, externe API’s of backgroundtasks.
In plaats van ze synchroon op te lossen, wacht het dependency-injectionsysteem tot ze zijn opgelost vóór injectie.
Hieronder een voorbeeld van een async dependency in FastAPI:
from fastapi import FastAPI, Depends
app = FastAPI()
# Async dependency
async def get_user():
# Simulate async I/O
await some_async_db_query()
return {"name": "Philip Jones"}
@app.get("/profile")
async def read_profile(user: dict = Depends(get_user)):
return {"user": user}
FastAPI handelt de asynchrone afhandeling van get_user automatisch af en injecteert het resultaat.
dependency-injector ondersteunt ook async levenscyclusbeheer met AsyncResource-providers.
from dependency_injector import containers, providers, resources
class AsyncDB:
async def connect(self):
print("Connected")
return self
async def disconnect(self):
print("Disconnected")
class AsyncDBResource(resources.AsyncResource):
async def init(self) -> AsyncDB:
db = AsyncDB()
return await db.connect()
async def shutdown(self, db: AsyncDB):
await db.disconnect()
class Container(containers.DeclarativeContainer):
db = providers.Resource(AsyncDBResource)
# Usage
async def main():
container = Container()
await container.init_resources()
db = await container.db() # Get the AsyncDB instance
# Use db here...
await container.shutdown_resources()
In de code hierboven:
-
resources.AsyncResourcewordt gebruikt voor async setup-/teardownlogica. -
await container.init_resources()initialiseert alle resources en roeptinitaan. -
await container.shutdown_resources()ruimt alle resources op. -
await container.db()haalt de daadwerkelijke resource-instantie op, namelijkAsyncDB.
Met asynchrone dependency injection houd je je applicatie performant door synchrone I/O in asynchrone routes te vermijden, en hoef je dependencies in je logica niet handmatig te awaiten.
Beveiligingsimplicaties
Hoewel dependency injection modulariteit en testbaarheid verbetert, kan het potentiële beveiligingsrisico’s introduceren als het niet zorgvuldig wordt beheerd.
Een belangrijk risico is dependency confusion, waarbij kwaadaardige pakketten legitieme nabootsen. Zo hebben aanvallers pakketten geüpload naar PyPI met namen die in interne bedrijfssystemen worden gebruikt om apps te misleiden de kwaadaardige versie te gebruiken.
Blijf veilig door nooit onbetrouwbare of door gebruikers aangestuurde componenten te injecteren en valideer configuratiebronnen, zoals omgevingsvariabelen, voordat je ze injecteert.
Gebruik requirements.txt- of poetry.lock-bestanden om exacte versies te vergrendelen, en automatiseer securityscans bij dependencywijzigingen. Zorg er daarnaast voor dat je je dependencies regelmatig op kwetsbaarheden controleert.
Het centraliseren van de controle over je dependencies is ook essentieel, omdat dit consistentie over je applicaties waarborgt en het toepassen van securitypatches of versie-updates makkelijker maakt. Het helpt om “shadow dependencies” te vermijden die op verschillende manieren door de codebase worden geïnjecteerd.
Verschillende tools kunnen risico’s detecteren en je securitypostuur verbeteren:
- Snyk: Scant op bekende kwetsbaarheden in Pythonpakketten en stelt fixes voor.
- Xygeni: Biedt bescherming voor CI/CD-pijplijnen.
- Safety: Controleert op onveilige pakketten en codekwetsbaarheden.
- Bandit: Een statische code-analyzer voor Python gericht op securityproblemen.
Toepassingen in de echte wereld
Dependency injection is niet alleen een theoretisch patroon; het wordt in allerlei productiesystemen gebruikt om complexiteit te beheren, flexibiliteit te verbeteren en testen te stroomlijnen.
Configuratie van webservices
In moderne webapplicaties speelt dependency injection een cruciale rol bij het beheren van standaardservices, zoals authenticatie, logging en database‑toegang.
Frameworks zoals FastAPI spelen een essentiële rol bij het oplossen van routes en dependencies.
Het onderstaande voorbeeld illustreert een gecentraliseerde authenticatie-implementatie in FastAPI.
from fastapi import Depends, HTTPException
def get_current_user(token: str = Depends(oauth2_scheme)):
user = verify_token(token)
if not user:
raise HTTPException(status_code=401)
return user
@app.get("/protected")
def protected_route(user: dict = Depends(get_current_user)):
return {"message": f"Hello {user['name']}"}
In de code hierboven wordt get_current_user() als routedependency geïnjecteerd, maar de logica voor het controleren van de token is gecentraliseerd en herbruikbaar.
Je kunt hetzelfde doen voor database‑toegang, zodat databasesessies automatisch worden beheerd en opgeschoond.
Hier is een voorbeeld van hoe je dependency injection kunt gebruiken om database‑toegang te centraliseren.
from sqlalchemy.ext.asyncio import AsyncSession
async def get_db_session() -> AsyncSession:
async with async_session() as session:
yield session
@app.get("/items")
async def read_items(db: AsyncSession = Depends(get_db_session)):
return await db.execute("SELECT * FROM items")
In de code hierboven is get_db_session een dependencyprovider. Wanneer de route read_items wordt aangeroepen, injecteert FastAPI automatisch de parameter db, zodat de route toegang heeft tot de database zonder de sessie handmatig te beheren of op te ruimen.
Test-driven development
Dependency injection maakt het makkelijk om te veranderen welke dependencies componenten ontvangen, wat belangrijk is bij testen.
Je kunt echte services tijdens tests vervangen door mocks, waardoor je code modularer en eenvoudiger te testen wordt.
FastAPI en dependency-injector ondersteunen het overriden van dependencies tijdens tests.
Hier is een voorbeeld in FastAPI.
from fastapi.testclient import TestClient
from main import app, get_db_session
def override_db():
return TestDBSession()
app.dependency_overrides[get_db_session] = override_db
client = TestClient(app)
response = client.get("/items")
assert response.status_code == 200
In het codevoorbeeld hierboven wordt get_db_session tijdens tests overschreven met een mockdatabase; je hoeft de productiecode dus niet te patchen of aan te passen.
Ook in depedency-injector kun je je echte database overschrijven met een mockdatabase voor testdoeleinden, zoals in het voorbeeld hieronder.
from containers import Container
def test_service_behavior():
container = Container()
container.db.override(providers.Factory(TestDB))
service = container.service()
assert service.get_data() == "mocked"
De methode override() vervangt de echte dependency door een mock: schoon, gecontroleerd en omkeerbaar.
Hier zijn een paar strategieën die je kunt gebruiken voor dependency overrides:
- Constructorinjectie: Zorg dat je mocks direct in de constructor meegeeft.
- Frameworkoverrides: Gebruik ingebouwde override-tools zoals
FastAPI.dependency_overridesin FastAPI ofdependency_injector.override()independency-injector. - Fixtures: Gebruik testframeworks zoals
pytestom herbruikbare mocks te maken en ze via fixtures te injecteren.
Best practices en antipatterns
Hoewel dependency injection is bedoeld om code modulair en eenvoudig testbaar te maken, kan het tot bugs leiden of complexiteit creëren.
Aanbevolen werkwijzen
Hier zijn wat best practices om je aan te houden bij het opzetten van dependencies:
- Programmeur naar interfaces, niet naar implementaties: Definieer je dependencies met abstracte basisklassen of interfaces in plaats van complexe implementaties. Dit maakt je code makkelijker te testen en stimuleert het gebruik van mocks in unittests.
- Geef de voorkeur aan compositie boven overerving: Stel objecten samen met geïnjecteerde dependencies in plaats van complexe classhiërarchieën te bouwen, om componenten klein en gefocust te houden.
- Gecentraliseerde dependencyconfiguratie: Gebruik één container of module om dependencies te definiëren en te beheren, waardoor overriden en testen eenvoudiger wordt.
- Vermijd circulaire dependencies: Ontwerp je dependencygraph zorgvuldig om cirkelverwijzingen te voorkomen, bijvoorbeeld door factoryfuncties of providers te gebruiken om instantiering uit te stellen.
Veelvoorkomende valkuilen
Vermijd de volgende valkuilen bij het gebruik van dependency injection.
- Service locator‑antipatroon: Vermijd een globale container om dynamisch dependencies op te halen; dit kan dependencies verbergen en code moeilijk te begrijpen en te testen maken.
- Over‑injectie: Injecteer niet te veel services in één class of functie om opgeblazen constructors en lastig te testen logica te voorkomen.
- Scopemismanagement en weglekkende state: Gebruik altijd de juiste scope om te voorkomen dat state tussen gebruikers of tests gedeeld wordt.
Dependency injection testen in Python
Dependency injection maakt het eenvoudig om mockobjecten in je componenten te injecteren en vereenvoudigt zo unittests. Dit laat je fundamentele componenten scheiden van testspecifieke.
Met dependency injection zijn tests deterministisch, hebben ze geen bijwerkingen en voeren ze sneller uit.
Hier is een voorbeeld dat laat zien hoe je dependency injection kunt gebruiken om mock-, stub- of fakeversies van die dependencies in je tests te injecteren.
class EmailService:
def send_email(self, to: str, message: str): ...
class Notifier:
def __init__(self, email_service: EmailService):
self.email_service = email_service
def alert(self, user_email):
self.email_service.send_email(user_email, "Alert!")
Vervang in tests EmailService door een mock:
class TestEmailService:
def send_email(self, to, message):
self.sent_to = to
def test_notifier_sends_email():
test_email = TestEmailService()
notifier = Notifier(test_email)
notifier.alert("test@example.com")
assert test_email.sent_to == "test@example.com"
Gebruik dependency injection zo vaak mogelijk en vermijd monkeypatching voor onderhoudbaarheid en om brekende wijzigingen te voorkomen wanneer interne namen veranderen.
Conclusie
Hoewel je voor kleine projecten misschien niet direct dependency injection nodig hebt, profiteer je naarmate het project groeit van de structuur en modulariteit die dependency injection biedt.
Vooruitkijkend is de toekomst van dependency injection in Python veelbelovend, met een verbeterd typesysteem en betere async lifecycle‑management.
Dependency injection beheersen is essentieel om een vaardige Pythonontwikkelaar te worden. Maar daar stopt het niet; er zijn nog veel andere geavanceerde concepten die je Pythonskills kunnen verhogen.
Om je leercurve te versnellen, zijn hier wat resources die je kunt bekijken.
Cursussen:
- Developing Python Packages
- Introduction to PySpark
- Object-Oriented Programming in Python
- Intermediate Object-Oriented Programming in Python
Blogposts:
Ervaren docent data science en biostatisticus met expertise in Python, R en machine learning.
FAQs
Wat is dependency injection?
Dependency injection is een ontwerppatroon dat je helpt schonere code te schrijven door dependencies binnen een class van buitenaf te laten aanreiken, waardoor je code makkelijker te beheren en aan te passen is.
Wat zijn de belangrijkste voordelen van dependency injection?
Dependency injection maakt je code modulair, testbaar en onderhoudbaar.
Hoe kan ik dependency injection in Python implementeren?
Je kunt dependency injection in Python handmatig implementeren of met libraries zoals dependency_injector.
Wat is asynchrone injectie?
Asynchrone injectie betekent het injecteren van dependencies die async def-functies zijn of het beheren van async resources zoals async databaseverbindingen, externe API’s of backgroundtasks.
Wat zijn de beveiligingsrisico’s van dependency injection en hoe ga ik daarmee om?
Een belangrijk risico is dependency confusion, waarbij kwaadaardige pakketten legitieme nabootsen. Ter voorkoming kun je tools zoals Snyk, Xygeni, Safety of Bandit gebruiken om je code en pakketten op kwetsbaarheden te scannen.

