Ga naar hoofdinhoud

Dependency injection in Python: bouw modulaire en testbare code

Leer hoe je je code modulair, testbaar en onderhoudbaar maakt door verschillende Pythondependency-injectionframeworks te begrijpen en te implementeren.
Bijgewerkt 2 jun 2026  · 14 min lezen

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, injector en punq.

  • Populaire webframeworks gaan er verschillend mee om: FastAPI heeft ingebouwde ondersteuning via Depends(). Flask en Django hebben een library zoals dependency-injector nodig.

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:

  • Container bepaalt hoe instanties worden aangemaakt

  • Singleton zorgt ervoor dat er maar één instantie van EmailService bestaat

  • Factory maakt een nieuwe UserNotifier met de EmailService automatisch 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 vertelt dependency-injector welke 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 de name uit 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 en Provide[...]-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.

Image showing the url of a running Flask appliction

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.

Image showing the Django application running.

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.

Image showing a running FastAPI application.

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

dependency-injector

Complexe applicaties in productie

Volledig uitgerust, snel en configureerbaar

Langdradig

injector

Eenvoudig en geschikt voor minimale applicaties

Schone syntaxis

Minder geavanceerd dan anderen.

pinject

Geavanceerde bindinglogica

Auto-wiring

Langzamer door introspectie

punq

Eenvoudige applicaties

Minimaal en snel

Beperkte features vergeleken met anderen

FastAPI Depends()

FastAPI-applicaties

Ingebouwde ondersteuning voor async en ook testbaar

Niet herbruikbaar buiten FastAPI

Flask-injector

Flaskapplicaties

Eenvoudige integratie met Flaskapplicaties

Afhankelijk van injector

django-injector

Djangoprojecten

Biedt dependency injection voor views en middleware

Afhankelijk van injector

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.AsyncResource wordt gebruikt voor async setup-/teardownlogica.

  • await container.init_resources() initialiseert alle resources en roept init aan.

  • await container.shutdown_resources() ruimt alle resources op.

  • await container.db() haalt de daadwerkelijke resource-instantie op, namelijk AsyncDB.

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 bedrijfs­systemen 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_overrides in FastAPI of dependency_injector.override() in dependency-injector.
  • Fixtures: Gebruik testframeworks zoals pytest om 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:

Blogposts:


Adejumo Ridwan Suleiman's photo
Author
Adejumo Ridwan Suleiman
LinkedIn

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.

Onderwerpen

Leer Python met DataCamp

Cursus

Introductie tot Python

4 Hr
6.9M
Leer de basis van data-analyse met Python in 4 uur. Deze online cursus laat je kennismaken met de Python-interface en populaire pakketten.
Bekijk detailsRight Arrow
Begin met de cursus
Meer zienRight Arrow
Gerelateerd

blog

AI vanaf nul leren in 2026: een complete gids van de experts

Ontdek alles wat je moet weten om in 2026 AI te leren, van tips om te beginnen tot handige resources en inzichten van industrie-experts.
Adel Nehme's photo

Adel Nehme

15 min

Meer zienMeer zien