Corso
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,injectorepunq. -
I framework web più diffusi la gestiscono in modo diverso: FastAPI ha il supporto integrato tramite
Depends(). Flask e Django richiedono una libreria comedependency-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:
-
Containerdefinisce come vengono create le istanze -
Singletongarantisce che esista una sola istanza diEmailService -
Factorycrea un nuovoUserNotifierconEmailServiceiniettato 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
@injectinietta automaticamente la dipendenza della funzione o classe. -
La classe
Provideindica adependency-injectorquale 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 ilnamedalla 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@injecte i markerProvide[...]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.

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.

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.

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 |
|
|
Applicazioni complesse in produzione |
Completo di funzionalità, veloce e configurabile |
Verboso |
|
Semplice e adatto ad applicazioni minime |
Sintassi pulita |
Meno avanzato di altri |
|
|
Logica di binding avanzata |
Auto-wiring |
Più lento a causa dell'introspezione |
|
|
Applicazioni semplici |
Minimale e veloce |
Funzionalità limitate rispetto ad altri |
|
|
Applicazioni FastAPI |
Supporto integrato per async, e anche testabile |
Non riutilizzabile fuori da FastAPI |
|
|
Applicazioni Flask |
Integrazione semplice con applicazioni Flask |
Dipende da |
|
|
Progetti Django |
Fornisce dependency injection per view e middleware |
Dipende da |
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 chiamainit. -
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_overridesin FastAPI odependency_injector.override()independency-injector. - Fixture: usa framework di testing come
pytestper 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:
- Developing Python Packages
- Introduction to PySpark
- Object-Oriented Programming in Python
- Intermediate Object-Oriented Programming in Python
Articoli del blog:
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à.


