Vai al contenuto principale

Dependency Injection in Python: crea codice modulare e testabile

Impara a rendere il tuo codice modulare, testabile e manutenibile comprendendo e implementando vari framework di dependency injection in Python.
Aggiornato 3 giu 2026  · 14 min leggi

La dependency injection è un pattern di progettazione in cui oggetti o servizi (cioè le dipendenze) vengono forniti a una classe dall'esterno, invece che essere creati internamente dalla classe stessa. Questo disaccoppia i componenti.

Usare la dependency injection in Python ha molti vantaggi, tra cui:

  • Modularità: il codice è suddiviso in parti più piccole e riutilizzabili.
  • Testabilità: è più facile testare il codice perché puoi sostituire la parte reale con dei mock.
  • Manutenibilità: puoi aggiornare o modificare parti del codice senza rompere il resto del sistema. 

In questo tutorial ti mostrerò come usare la dependency injection in Python con esempi semplici e pratici.

TL;DR

  • La dependency injection è un pattern in cui una classe riceve le sue dipendenze dall'esterno invece di crearle da sola. Rende il codice più modulare.

  • Puoi iniettare dipendenze tramite il costruttore, un metodo setter o direttamente in un metodo; Python lo supporta manualmente o tramite framework come dependency-injector, injector e punq.

  • I framework web più diffusi la gestiscono in modo diverso: FastAPI ha il supporto integrato tramite Depends(). Flask e Django richiedono una libreria come dependency-injector.

Capire la Dependency Injection in Python

Per capire come implementare la dependency injection, è essenziale afferrare i principi chiave che la governano.

La dependency injection si basa sul principio dell'Inversion of Control (IoC), che significa che, invece di creare e gestire le proprie dipendenze, una classe le riceve da una fonte esterna. Questo aiuta a focalizzarsi su un singolo compito e rende il codice pulito.

L'iniezione delle dipendenze in Python include questi metodi comuni, sebbene ce ne siano altri:

  • Constructor injection: passa le dipendenze tramite il costruttore della classe.
  • Setter injection: imposta le dipendenze usando un metodo setter dopo la creazione dell'oggetto.
  • Method injection: passa le dipendenze direttamente al metodo che ne ha bisogno.

Perché usare la Dependency Injection in Python?

Come sviluppatore, comprendere e implementare la dependency injection può fare una grande differenza nel design del tuo codice. Vediamo alcuni vantaggi della dependency injection.

Modularità, riusabilità e flessibilità

La dependency injection ti consente di suddividere il codice in componenti più piccoli e mirati, offrendo maggiore flessibilità e modularità. 

Ogni parte o componente gestisce un compito specifico e si affida a dipendenze esterne, il che ne facilita il riuso in parti diverse della tua applicazione o di quella di altri

Per esempio, se hai una classe che invia messaggi, invece di inserire direttamente la logica per email o SMS nel servizio, puoi iniettare una classe di invio email o SMS. 

Ora la tua classe che invia messaggi non si preoccupa di come il messaggio venga inviato, ma usa semplicemente il sender che le viene fornito. Questo implica che puoi usare il tuo sender con diversi invii email o SMS. 

Manutenibilità e testabilità

Con la dependency injection puoi sostituire facilmente le dipendenze reali con dati mock durante i test. Questo semplifica la scrittura di unit test senza dipendere da API o database. 

Supponiamo, come esercizio mentale, di avere una classe che salva dati in un database. Per i test, puoi iniettare un database mock invece di uno reale per evitare di accoppiare il test al database di produzione.

La dependency injection ti permette di modificare parti specifiche del codice senza rompere l'intero sistema. È particolarmente utile quando devi cambiare provider di pagamento in un'applicazione, ad esempio da Stripe a PayPal, senza modificare il resto del codice.

Inversion of control e dipendenze intercambiabili

L'idea dell'inversion of control è questa: la dependency injection sposta la responsabilità della creazione e gestione delle dipendenze su una fonte esterna. 

Questo implica che puoi sostituire facilmente un'implementazione con un'altra; per esempio, se hai una classe ReportGenerator che formatta ed esporta report. 

Invece che sia la classe a decidere il formato di esportazione, puoi iniettarle una classe di esportazione, come PDFExporter, ExcelExporter o CSVExporter.

La classe ReportGenerator non si cura di come avvenga l'esportazione, quindi per cambiare formato ti basta sostituire l'exporter che inietti, senza modificare affatto ReportGenerator

Implementare la Dependency Injection in Python

Ora che hai un'idea chiara di cos'è la dependency injection e perché è utile, vediamo come implementarla in Python. Partiamo dal metodo più semplice: la dependency injection manuale.

Dependency injection manuale

Con la dependency injection manuale, crei tu stesso le dipendenze e le passi alla classe o funzione che ne ha bisogno. 

Questo metodo funziona bene per applicazioni piccole, ma quando l'app cresce dovresti usare un framework di dependency injection per evitare errori.

Ecco un esempio di codice senza 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!")

Nell'esempio sopra, UserNotifier è strettamente accoppiata a EmailService. Non puoi sostituire o fare facilmente il mock di EmailService per i test. 

Ecco un'altra versione, ma con dependency injection manuale.

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!")

La versione sopra è flessibile e ti consente di iniettare vari servizi di messaggistica; puoi anche sostituire EmailService con un mock nei test.

Usare il framework dependency-injector

Quando l'applicazione cresce, gestire le dipendenze diventa complesso e disordinato. 

Il framework dependency-injector fornisce un approccio strutturato.

Architettura container-provider

Il framework dependency-injector funziona secondo un'architettura container-provider. 

  • Container: è il registro centrale delle dipendenze della tua applicazione.
  • Providers: definiscono come vengono create le dipendenze.
  • Configuration: consente di modificare impostazioni e iniettarle negli oggetti. 
  • Overriding: ti permette di sostituire dipendenze senza cambiare il codice dell'applicazione, soprattutto durante i test. 
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!")

Nell'esempio sopra:

  • Container definisce come vengono create le istanze

  • Singleton garantisce che esista una sola istanza di EmailService

  • Factory crea un nuovo UserNotifier con EmailService iniettato automaticamente.

Meccanismo di wiring

Puoi usare decorator come @inject per ridurre la necessità di passare manualmente le dipendenze. 

Ecco un'altra versione dell'esempio precedente:

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()

Nell'attuale versione:

  • Senza aggiungere manualmente la dipendenza, il decorator @inject inietta automaticamente la dipendenza della funzione o classe.

  • La classe Provide indica a dependency-injector quale dipendenza iniettare.

Questo approccio elimina la necessità di collegare manualmente le dipendenze dal container. 

È utile in applicazioni più grandi in cui i servizi possono dipendere da più componenti (quindi la necessità di inizializzarli automaticamente).

Usare la Dependency Injection nei framework Python più diffusi

La maggior parte dei framework Python moderni facilita l'uso della dependency injection, permettendoti di scrivere codice più pulito e più facile da testare. 

In questa sezione vedrai come si applica la dependency injection in Flask, Django e FastAPI.

Dependency injection in Flask

Flask non ha il supporto integrato per la dependency injection, ma puoi implementarla usando la libreria dependency-injector seguendo questi passaggi.

Passo 1: definisci i tuoi servizi

class GreetingService:
    def get_greeting(self, name):
        return f"Hello, {name}!"

Passo 2: configura il container di dependency injection

from dependency_injector import containers, providers

class Container(containers.DeclarativeContainer):
    greeting_service = providers.Factory(GreetingService)

Passo 3: crea l'applicazione Flask e inietta le dipendenze.

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)

Dal codice sopra:

  • La funzione name = request.args.get(“name”, “World”) estrae il name dalla query string e usa ”World” come default se non è fornito. 

  • return greeting_service.get_greeting(name) chiama il metodo sul servizio iniettato e restituisce un messaggio di saluto. 

  • container.wire(modules=[__name__]) dice al dependency injector di scansionare questo modulo per i decorator @inject e i marker Provide[...] per collegarli ai provider effettivi. 

Passo 4: testa l'applicazione

Esegui l'applicazione dal terminale con il comando python <name_of_file>.py e visita l'URL http://localhost:5000/greet?name=Jacob

Dovresti vedere quanto segue.

Immagine che mostra l'URL di un'applicazione Flask in esecuzione

Dependency injection in Django

Proprio come Flask, anche Django non offre la dependency injection integrata. Tuttavia, puoi usare le class-based view, il middleware e la struttura delle app per integrarla manualmente o con la libreria dependency-injector

Ecco una procedura passo passo per implementare la dependency injection in Django. 

Passo 1: definisci il tuo servizio

Crea un servizio semplice con la tua logica di business in un nuovo file services.py nella cartella della tua app. 

# myapp/services.py
class GreetingService:
    def get_greeting(self, name):
        return f"Hello, {name}!"

Passo 2: configura il container del dependency injector

All'interno della directory dell'applicazione, crea un container usando la libreria dependency-injector in un nuovo file containers.py.

# myapp/containers.py
from dependency_injector import containers, providers
from .services import GreetingService

class Container(containers.DeclarativeContainer):
    greeting_service = providers.Factory(GreetingService)

Passo 3: crea una View con dipendenze iniettate

Usa il decorator @inject nella tua view per ricevere le dipendenze 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)

Passo 4: aggiorna urls.py

Nella cartella del progetto, vai al file urls.py e collega il dependency injector al sistema di routing di Django.

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),
]

Passo 5: collega il container in apps.py 

Vai in apps.py e collega il container all'applicazione. 

 # 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"])

Poi vai nella configurazione dell'app in INSTALLED_APPS e aggiungi la tua applicazione.

# settings.py
INSTALLED_APPS = [
    'myapp.apps.MyAppConfig',
    ...
]

Passo 6: testa la View

Avvia il server Django e accedi alla view tramite l'URL http://localhost:8000/greet/?name=Django

Dovresti vedere quanto segue.

Immagine che mostra l'applicazione Django in esecuzione.

Dependency Injection in FastAPI

FastAPI ha il supporto integrato per la dependency injection tramite il marker speciale Depends() che inietta qualunque cosa restituisca una funzione. 

Ecco una guida passo passo su come procedere con la dependency injection in FastAPI.

Passo 1: definisci un servizio

class GreetingService:
    def get_greeting(self, name: str) -> str:
        return f"Hello, {name}!"

Passo 2: crea la funzione di dipendenza

def get_greeting_service():
    return GreetingService()

Passo 3: configura l'app FastAPI e usa la Dependency Injection

app = FastAPI()

@app.get("/greet")
def greet(
    name: str = "World", service: GreetingService = Depends(get_greeting_service)
):
    return {"message": service.get_greeting(name)}

Passo 4: esegui e testa l'applicazione

Avvia il server con il comando uvicorn main:app –reload e visita l'URL http://localhost:8000/greet?name=FastAPI. Dovresti vedere la seguente risposta.

Immagine che mostra un'applicazione FastAPI in esecuzione.

Confronto tra framework di Dependency Injection per Python

Esistono vari framework di dependency injection per Python, ognuno con i propri punti di forza, sintassi e casi d'uso. La scelta del framework giusto dipende dalla dimensione e dalla struttura del progetto. 

Ecco un confronto tra alcuni framework di dependency injection comuni in Python.

Framework

Migliore per

Punti di forza

Limitazioni

dependency-injector

Applicazioni complesse in produzione

Completo di funzionalità, veloce e configurabile

Verboso

injector

Semplice e adatto ad applicazioni minime

Sintassi pulita

Meno avanzato di altri

pinject

Logica di binding avanzata

Auto-wiring

Più lento a causa dell'introspezione

punq

Applicazioni semplici

Minimale e veloce

Funzionalità limitate rispetto ad altri

FastAPI Depends()

Applicazioni FastAPI

Supporto integrato per async, e anche testabile

Non riutilizzabile fuori da FastAPI

Flask-injector

Applicazioni Flask

Integrazione semplice con applicazioni Flask

Dipende da injector

django-injector

Progetti Django

Fornisce dependency injection per view e middleware

Dipende da injector

Pattern di implementazione avanzati

Quando la codebase della tua applicazione cresce, la dependency injection diventa difficile da mantenere. 

Puoi usare tecniche avanzate di dependency injection per gestire attività come risorse con scope di richiesta, cleanup e separazione delle responsabilità. 

Dipendenze con scope

Le dipendenze con scope ti permettono di controllare quanto a lungo persiste una dipendenza, ad esempio quando vuoi un nuovo oggetto per richiesta, un oggetto condiviso per l'intero ciclo di vita dell'app o il cleanup automatico di risorse come le connessioni al database. 

La maggior parte dei framework di dependency injection supporta scope come:

  • Singleton: un'istanza per l'intera app.
  • Factory: una nuova istanza ogni volta che viene iniettata.
  • Thread-local o request: un'unica istanza per thread/richiesta.
  • Resource: gestita con logica di setup e teardown.

Di seguito un esempio di dipendenze con scope con container annidati usando 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")

Nell'esempio sopra, MyResource imita una classe che richiede setup e teardown, come aprire/chiudere un file o un database.

Il container fornisce la risorsa usando providers.Resource e quando usi with container.resource() entra ed esce correttamente dal contesto.

Questo pattern garantisce che risorse come file handle, connessioni al database o sessioni di rete siano gestite in modo pulito.

Immagina un endpoint API che ha bisogno di una sessione di database per la richiesta corrente o di un logger con contesto specifico della richiesta. 

Con le dipendenze con scope, il framework crea un container con scope di richiesta quando la richiesta inizia, risolvendo tutte le dipendenze da quello scope. 

Quando la richiesta termina, eventuali cleanup necessari, come la chiusura delle sessioni di database, avvengono automaticamente. Questo assicura che gli stati non siano condivisi tra richieste ed evita perdite di risorse.

Iniezione asincrona

Per gestire in modo efficiente i task I/O-bound, hai bisogno dell'esecuzione asincrona. I sistemi di dependency injection in framework come FastAPI e dependency-injector offrono l'iniezione asincrona usando async/await.

Per iniezione asincrona si intende l'iniezione di dipendenze che sono funzioni async def o la gestione di async resources come connessioni a database asincroni, API esterne o task in background. 

Invece di risolverle in modo sincrono, il sistema di dependency injection attende che vengano risolte prima di iniettarle. 

Di seguito un esempio di dipendenza async 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 gestisce automaticamente la risoluzione async di get_user e ne inietta il risultato.

dependency-injector supporta anche la gestione del ciclo di vita async usando i provider AsyncResource.

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()

Nel codice sopra:

  • resources.AsyncResource è usato per la logica di setup/teardown asincrona.

  • await container.init_resources() inizializza tutte le risorse e chiama init.

  • await container.shutdown_resources() pulisce tutte le risorse.

  • await container.db() ottiene l'istanza reale della risorsa, cioè AsyncDB.

Usare l'iniezione async aiuta a mantenere l'app performante evitando I/O sincrono in route asincrone, ed elimina la necessità di attendere manualmente le dipendenze nella tua logica. 

Implicazioni di sicurezza

Sebbene la dependency injection migliori modularità e testabilità, può introdurre rischi di sicurezza se non gestita con attenzione.

Un rischio significativo è la dependency confusion, in cui pacchetti malevoli imitano quelli legittimi. Per esempio, alcuni attaccanti hanno caricato pacchetti su PyPI con nomi usati in sistemi interni aziendali per indurre le app a usare la versione malevola.

Per restare al sicuro, assicurati di non iniettare mai componenti non attendibili o controllati dall'utente e valida le fonti di configurazione, come le variabili d'ambiente, prima che vengano iniettate.

Usa file requirements.txt o poetry.lock per bloccare le versioni esatte, e automatizza la scansione di sicurezza quando cambiano le dipendenze. Inoltre, verifica regolarmente le tue dipendenze per vulnerabilità.

Centralizzare il controllo delle dipendenze è essenziale, perché assicura coerenza tra le applicazioni e facilita l'applicazione di patch di sicurezza o aggiornamenti di versione. Aiuta a evitare "dipendenze ombra" iniettate in modi diversi nella codebase. 

Diversi strumenti possono rilevare rischi e migliorare la tua postura di sicurezza:

  • Snyk: analizza i pacchetti Python per vulnerabilità note e suggerisce correzioni.
  • Xygeni: offre protezione per le pipeline CI/CD.
  • Safety: controlla pacchetti insicuri e vulnerabilità del codice.
  • Bandit: un analizzatore statico del codice per Python focalizzato su problemi di sicurezza.

Applicazioni reali

La dependency injection non è solo un pattern teorico; viene usata in vari sistemi di produzione per gestire la complessità, migliorare la flessibilità e semplificare i test. 

Configurazione dei servizi web

Nelle applicazioni web moderne, la dependency injection ha un ruolo cruciale nella gestione di servizi standard come autenticazione, logging e accesso al database. 

Framework come FastAPI svolgono un ruolo essenziale nella risoluzione di route e dipendenze.

L'esempio seguente illustra un'implementazione centralizzata dell'autenticazione 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']}"}

Nel codice sopra, get_current_user() è iniettata come dipendenza della route, ma la logica di verifica del token è centralizzata e riutilizzabile.

Puoi fare lo stesso per l'accesso al database, in modo che le sessioni siano gestite e pulite automaticamente. 

Ecco un esempio di come usare la dependency injection per centralizzare l'accesso al database.

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")

Nel codice sopra, get_db_session è un provider di dipendenze. Quando viene chiamata la route read_items, FastAPI inietta automaticamente il parametro db, consentendo alla route di accedere al database senza gestire manualmente setup o teardown della sessione. 

Test-driven development

La dependency injection rende facile cambiare le dipendenze che i componenti ricevono, il che è importante in fase di test. 

Puoi sostituire i servizi reali con mock durante i test, rendendo il codice più modulare e più facile da testare. 

FastAPI e dependency-injector supportano la sostituzione delle dipendenze in fase di test. 

Ecco un esempio 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

Nell'esempio di codice sopra, get_db_session è sovrascritta con un database mock durante i test; quindi non c'è bisogno di modificare il codice di produzione.

Anche in depedency-injector puoi sostituire il tuo database reale con un database mock per i test, come nell'esempio seguente.

from containers import Container

def test_service_behavior():
    container = Container()
    container.db.override(providers.Factory(TestDB))

    service = container.service()
    assert service.get_data() == "mocked"

Il metodo override() sostituisce la dipendenza reale con un mock, in modo pulito, controllato e reversibile. 

Ecco alcune strategie da usare per le override delle dipendenze:

  • Constructor injection: assicurati di passare i mock direttamente al costruttore.
  • Override del framework: usa gli strumenti di override integrati come FastAPI.dependency_overrides in FastAPI o dependency_injector.override() in dependency-injector.
  • Fixture: usa framework di testing come pytest per creare mock riutilizzabili e iniettarli tramite fixture.

Best practice e anti-pattern

Sebbene la dependency injection sia pensata per rendere il codice modulare e facile da testare, può portare a bug o creare complessità nel codice. 

Pratiche raccomandate

Ecco alcune best practice da seguire quando crei dipendenze:

  • Programma verso interfacce, non implementazioni: definisci le dipendenze usando classi astratte o interfacce anziché implementazioni complesse. Rende il codice più facile da testare e incoraggia l'uso di mock nei test unitari.
  • Preferisci la composizione all'ereditarietà: piuttosto che costruire gerarchie di classi complesse, componi oggetti usando dipendenze iniettate per mantenere i componenti piccoli e focalizzati.
  • Configurazione centralizzata delle dipendenze: utilizza un unico container o modulo per definire e gestire le dipendenze, facilitando override e test.
  • Evita le dipendenze circolari: progetta con cura il grafo delle dipendenze per prevenire riferimenti circolari usando funzioni factory o provider per ritardare l'istanziazione.

Errori comuni

Ecco alcune insidie comuni da evitare quando usi la dependency injection.

  • Anti-pattern del service locator: evita di usare un container globale per recuperare dinamicamente le dipendenze, perché può nasconderle e rendere il codice difficile da capire e testare.
  • Over-injection: evita di iniettare troppi servizi in una singola classe o funzione per non avere costruttori gonfi e logica difficile da testare.
  • Gestione scorretta dello scope e perdita di stato: usa sempre lo scope corretto per evitare condivisione di stato tra utenti o test.

Testing della Dependency Injection in Python

La dependency injection rende facile iniettare oggetti mock nei componenti, semplificando così gli unit test. Ciò ti permette di separare i componenti fondamentali da quelli specifici per i test.

Con la dependency injection, i test sono deterministici, senza effetti collaterali e si eseguono più velocemente. 

Ecco un esempio che mostra come usare la Dependency Injection per iniettare versioni mock, stub o fake di tali dipendenze nei test. 

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!")

Nei test, sostituisci EmailService con un 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"

Assicurati di usare spesso la dependency injection ed evita il monkey patching per la manutenibilità e per evitare rotture quando cambiano i nomi interni.

Conclusione

Anche se potresti non aver bisogno della dependency injection inizialmente per progetti piccoli, man mano che il progetto cresce e scala, puoi beneficiare della struttura e della modularità offerte dalla dependency injection. 

Guardando avanti, il futuro della dependency injection in Python è promettente, e possiamo aspettarci un'evoluzione con un sistema di tipi migliorato e una gestione del ciclo di vita async potenziata.

Padroneggiare la dependency injection è essenziale per diventare uno sviluppatore Python fluente. Ma non finisce qui: molti altri concetti avanzati possono elevare le tue skill in Python. 

Per aiutarti ad accelerare il percorso di apprendimento, ecco alcune delle nostre risorse da consultare.

Corsi:

Articoli del blog:


Adejumo Ridwan Suleiman's photo
Author
Adejumo Ridwan Suleiman
LinkedIn

Docente di data science esperto e biostatistico con competenze in Python, R e machine learning.

FAQs

Che cos'è la dependency injection?

La dependency injection è un pattern di progettazione che ti permette di scrivere codice più pulito creando dipendenze all'interno di una classe che possono essere passate dall'esterno, rendendo il codice più facile da gestire e modificare.

Quali sono i principali vantaggi della dependency injection?

La dependency injection rende il codice modulare, testabile e manutenibile.

Come posso implementare la dependency injection in Python?

Puoi implementare la dependency injection in Python manualmente oppure usando librerie come dependency_injector.

Che cos'è l'iniezione asincrona?

Per iniezione asincrona si intende l'iniezione di dipendenze che sono funzioni async def o la gestione di async resources come connessioni a database asincroni, API esterne o task in background.

Quali sono i rischi di sicurezza della dependency injection e come gestirli?

Un rischio significativo è la dependency confusion, in cui pacchetti malevoli imitano quelli legittimi. Per prevenirla, puoi usare strumenti come Snyk, Xygeni, Safety o Bandit per analizzare codice e pacchetti alla ricerca di vulnerabilità.

Argomenti

Impara Python con DataCamp

Corso

Introduzione a Python

4 h
6.9M
Impara le basi dell’analisi dei dati con Python in sole 4 ore. Questo corso online ti introduce all’interfaccia di Python e ai pacchetti più usati.
Vedi dettagliRight Arrow
Inizia il corso
Mostra altroRight Arrow
Correlato

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

Mostra altroMostra altro