Cours
L'injection de dépendances est un modèle de conception dans lequel des objets ou des services (nous entendons ici les dépendances) sont fournis à une classe depuis l'extérieur, au lieu d'être créés en interne par la classe. Cela permet de dissocier les composants.
L'utilisation de l'injection de dépendances en Python présente de nombreux avantages, tels que :
- Modularité : Votre code est divisé en parties plus petites et réutilisables.
- Testabilité : Il est plus facile de tester votre code, car vous pouvez remplacer les éléments réels par des éléments fictifs.
- Facilité de maintenance : Vous pouvez mettre à jour ou modifier certaines parties de votre code sans perturber le reste du système.
Dans ce tutoriel, je vais vous montrer comment utiliser l'injection de dépendances en Python à travers des exemples simples et pratiques.
Comprendre l'injection de dépendances en Python
Pour comprendre comment mettre en œuvre l'injection de dépendances, il est essentiel de bien saisir les principes clés qui la régissent.
L'injection de dépendances repose sur un principe appelé « inversion de contrôle » (IoC), qui signifie qu'au lieu de créer et de gérer ses dépendances, une classe les reçoit d'une source externe. Cela permet de se concentrer sur une seule tâche et rend votre code plus clair.
L'injection de dépendances en Python comprend les méthodes courantes suivantes, bien qu'il en existe d'autres :
- Injection du constructeur : Transmettez les dépendances via le constructeur de classe.
- Injection du setter : Définissez les dépendances à l'aide d'une méthode de définition après la création de l'objet.
- Injection de méthode : Transmettez les dépendances directement à la méthode qui en a besoin.
Pourquoi utiliser l'injection de dépendances en Python ?
En tant que développeur, la compréhension et la mise en œuvre de l'injection de dépendances peuvent faire une grande différence dans la conception de votre code. Examinons quelques avantages de l'injection de dépendances.
Modularité, réutilisabilité et flexibilité
L'injection de dépendances vous permet de décomposer votre code en composants plus petits et plus ciblés, ce qui offre une plus grande flexibilité et modularité.
Chaque partie ou composant exécute une tâche spécifique et dépend de composants externes, ce qui facilite sa réutilisation dans différentes parties de votre application ou de celle d'un autre développeur.
Par exemple, si vous avez une classe qui envoie des messages. Au lieu d'intégrer une logique d'envoi d'e-mails ou de SMS directement dans votre service, vous pouvez injecter une classe d'expéditeur d'e-mails ou de SMS.
À présent, votre classe qui envoie des messages ne se soucie pas de la manière dont le message est envoyé, mais utilise simplement l'expéditeur qui lui est fourni. Cela signifie que vous pouvez utiliser votre expéditeur de messages avec différents expéditeurs d'e-mails ou de SMS.
Maintenabilité et testabilité
Lorsque vous utilisez l'injection de dépendances, vous pouvez facilement remplacer les dépendances réelles par des données fictives pendant les tests. Cela simplifie la rédaction de tests unitaires sans dépendre d'API ou de bases de données.
Supposons, à titre d'exercice de réflexion, que vous disposiez d'une classe qui enregistre des données dans une base de données. À des fins de test, vous pouvez injecter une base de données factice à la place d'une base de données réelle afin d'éviter de lier votre test à la base de données de production.
L'injection de dépendances vous permet de modifier des parties spécifiques de votre code sans perturber l'ensemble du système. Cela est particulièrement utile lorsque vous devez changer de prestataire de paiement dans une application, par exemple passer de Stripe à PayPal, sans modifier le reste de votre code.
Inversion de contrôle et dépendances interchangeables
Le concept d'inversion de contrôle est le suivant : l'injection de dépendances transfère la responsabilité de la création et de la gestion des dépendances à une source externe.
Cela signifie que vous pouvez facilement remplacer une implémentation par une autre. Par exemple, si vous disposez d'une classe ReportGenerator qui formate et exporte des rapports.
Au lieu que la classe décide du format d'exportation, vous pourriez y injecter une classe d'exportation, telle que PDFExporter, ExcelExporter ou CSVExporter.
La classe ReportGenerator ne se soucie pas de la manière dont l'exportation s'effectue. Par conséquent, pour modifier un format, il suffit de remplacer l'exportateur que vous injectez, sans modifier l'ReportGenerator.
Mise en œuvre de l'injection de dépendances en Python
Maintenant que vous avez une idée claire de ce qu'est l'injection de dépendances et de son utilité, voyons comment vous pouvez l'implémenter en Python. Commençons par la méthode la plus simple : l'injection manuelle des dépendances.
Injection de dépendance manuelle
Dans l'injection de dépendances manuelle, vous créez vous-même les dépendances et les transmettez à la classe ou à la fonction qui en a besoin.
Cette méthode est efficace pour les petites applications, mais à mesure que votre application évolue, il est recommandé d'utiliser un framework d'injection de dépendances afin d'éviter les erreurs.
Voici un exemple de code sans injection de dépendance
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!")
Dans l'exemple ci-dessus, UserNotifier est étroitement lié à EmailService. Il n'est pas facile de remplacer ou de simuler l'EmailService pour effectuer des tests.
Voici une autre version, mais avec injection de dépendances manuelle.
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 version ci-dessus est flexible et vous permet d'intégrer divers services de messagerie ; vous pouvez également remplacer EmailService par une simulation lors des tests.
Utilisation du framework d'injection de dépendances
À mesure que votre application se développe, la gestion des dépendances devient complexe et fastidieuse.
Le cadre dependency-injector cadre offre une approche structurée.
Architecture de fournisseur de conteneurs
Le framework d'dependency-injector s fonctionne selon une architecture conteneur-fournisseur.
- Conteneur: Il s'agit du registre central pour les dépendances de votre application.
- Fournisseurs: Ceci définit la manière dont vos dépendances sont créées.
- Configuration: Cela vous permet de modifier les paramètres et de les injecter dans des objets.
- Remplacement: Permet de remplacer des dépendances sans modifier le code de l'application, en particulier pendant les 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!")
Dans l'exemple ci-dessus :
-
Containerdéfinit comment les instances sont créées -
Singletongarantit qu'il n'existe qu'une seule instance d'EmailService -
Factorycrée une nouvelle instance d'UserNotifieravec l'EmailServiceinjectée automatiquement.
Mécanisme de câblage
Vous pouvez utiliser des décorateurs tels que @inject pour réduire le besoin de passer manuellement les dépendances.
Voici une autre version de l'exemple précédent :
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()
Dans la version actuelle ;
-
Sans ajouter manuellement la dépendance, le décorateur d'
@injects injecte automatiquement la dépendance de fonction ou de classe. -
La classe
Provideindique à l'dependency-injectorquelle dépendance injecter.
Cette approche élimine le besoin de dépendances de câblage manuelles depuis le conteneur.
Cela est utile dans les applications plus importantes où les services peuvent dépendre de plusieurs composants (d'où la nécessité de les initialiser automatiquement).
Utilisation de l'injection de dépendances dans les frameworks Python courants
La plupart des frameworks Python modernes facilitent l'utilisation de l'injection de dépendances, ce qui vous permet d'écrire un code plus propre et plus facile à tester.
Dans cette section, vous apprendrez comment l'injection de dépendances est appliquée dans Flask, Django et FastAPI.
Injection de dépendances dans Flask
Flask ne prend pas en charge l'injection de dépendances de manière native, mais vous pouvez l'implémenter à l'aide de la bibliothèque dependency-injector en suivant les étapes ci-dessous.
Étape 1 : Définissez vos services
class GreetingService:
def get_greeting(self, name):
return f"Hello, {name}!"
Étape 2 : Configurez votre conteneur d'injection de dépendances.
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
greeting_service = providers.Factory(GreetingService)
Étape 3 : Veuillez créer l'application Flask et injecter les dépendances.
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)
À partir du code ci-dessus :
-
La fonction
name = request.args.get(“name”, “World”)extrait l'namede la chaîne de requête et utilise par défaut”World”si aucune valeur n'est fournie. -
return greeting_service.get_greeting(name)appelle la méthode sur le service injecté et renvoie un message de bienvenue. -
container.wire(modules=[__name__])indique à l'injecteur de dépendances d'analyser ce module à la recherche de décorateurs d'@injects et de marqueurs d'Provide[...]afin de les relier aux fournisseurs réels.
Étape 4 : Veuillez tester l'application.
Veuillez exécuter l'application sur votre terminal à l'aide de la commande « python .py », puis rendez-vous à l'adresse URL « http://localhost:5000/greet?name=Jacob ».
Vous devriez voir ce qui suit.

Injection de dépendances dans Django
Tout comme Flask, Django ne propose pas d'injection de dépendances intégrée. Cependant, vous pouvez utiliser ses vues basées sur les classes, ses middlewares et sa structure d'application pour intégrer l'injection de dépendances manuellement ou à l'aide de la bibliothèque dependency-injector.
Voici une procédure étape par étape pour implémenter l'injection de dépendances dans Django.
Étape 1 : Définissez votre service
Créez un service simple contenant votre logique métier dans un nouveau fichier services.py dans votre dossier app.
# myapp/services.py
class GreetingService:
def get_greeting(self, name):
return f"Hello, {name}!"
Étape 2 : Configurer le conteneur d'injection de dépendances
Dans le répertoire de votre application, veuillez créer un conteneur à l'aide de la bibliothèque dependency-injector dans un nouveau fichier containers.py.
# myapp/containers.py
from dependency_injector import containers, providers
from .services import GreetingService
class Container(containers.DeclarativeContainer):
greeting_service = providers.Factory(GreetingService)
Étape 3 : Créer une vue avec des dépendances injectées
Veuillez utiliser le décorateur @inject dans votre vue pour recevoir les dépendances dans 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)
Étape 4 : Mettre à jour le fichier urls.py
Dans votre dossier de projet, accédez au fichier urls.py et connectez votre injecteur de dépendances au système de routage URL de 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),
]
Étape 5 : Connectez le conteneur dans apps.py
Veuillez vous rendre sur apps.py et connecter le conteneur à l'application.
# 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"])
Veuillez ensuite accéder à la configuration de votre application dans INSTALLED_APPS et ajouter votre application.
# settings.py
INSTALLED_APPS = [
'myapp.apps.MyAppConfig',
...
]
Étape 6 : Tester la vue
Démarrez votre serveur Django et accédez à la vue via l'URL http://localhost:8000/greet/?name=Django.
Vous devriez voir ce qui suit.

Injection de dépendances dans FastAPI
FastAPI intègre la prise en charge de l'injection de dépendances grâce au marqueur spécial ` Depends() `, qui injecte tout ce que renvoie une fonction.
Voici un guide étape par étape expliquant comment procéder à l'injection de dépendances FastAPI.
Étape 1 : Définir un service
class GreetingService:
def get_greeting(self, name: str) -> str:
return f"Hello, {name}!"
Étape 2 : Créer la fonction de dépendance
def get_greeting_service():
return GreetingService()
Étape 3 : Configurer l'application FastAPI et utiliser l'injection de dépendances
app = FastAPI()
@app.get("/greet")
def greet(
name: str = "World", service: GreetingService = Depends(get_greeting_service)
):
return {"message": service.get_greeting(name)}
Étape 4 : Exécutez et testez l'application.
Veuillez démarrer votre serveur à l'aide de la commande « uvicorn main:app –reload » et rendez-vous à l'adresse URL « http://localhost:8000/greet?name=FastAPI ». Vous devriez voir la réponse suivante.

Comparaison des frameworks d'injection de dépendances Python
Il existe plusieurs frameworks de dépendances Python, chacun avec ses propres atouts, syntaxe et cas d'utilisation. Le choix du cadre approprié dépend de la taille et de la structure de votre projet.
Voici une comparaison de quelques frameworks Python courants pour l'injection de dépendances.
|
Cadre |
Cordialement, Pour |
Points forts |
Restrictions |
|
|
Applications complexes dans la production |
Complet, rapide et configurable |
Verbeux |
|
Simple et adapté aux applications minimales |
Syntaxe claire |
Moins avancé que les autres. |
|
|
Logique de liaison avancée |
Câblage automatique |
Ralentissement dû à l'introspection |
|
|
Applications simples |
Minimaliste et rapide |
Fonctionnalités limitées par rapport à d'autres produits |
|
|
Applications FastAPI |
Prise en charge intégrée de l'asynchronisme et possibilité de test. |
Non réutilisable en dehors de FastAPI |
|
|
Applications Flask |
Intégration aisée avec les applications Flask |
Dépend de |
|
|
Projets Django |
Fournit l'injection de dépendances pour les vues et les intergiciels. |
Dépend de |
Modèles de mise en œuvre avancés
À mesure que le code de votre application s'étoffe, la gestion des dépendances devient plus complexe.
Vous pouvez utiliser l'injection de dépendances avancée pour gérer des tâches telles que les ressources au niveau de la requête, le nettoyage et la séparation des préoccupations.
Dépendances limitées
Les dépendances limitées vous permettent de contrôler la durée de persistance d'une dépendance, par exemple lorsque vous souhaitez un nouvel objet par requête, un objet partagé pendant toute la durée de vie de l'application ou le nettoyage automatique de ressources telles que les connexions à la base de données.
La plupart des frameworks d'injection de dépendances prennent en charge des portées telles que :
- Singleton : Une instance pour l'ensemble de l'application.
- Usine : Une nouvelle instance est créée à chaque injection.
- Local au thread ou à la requête : Une seule instance par thread/demande.
- Ressource : Géré avec une logique de configuration et de démontage.
Voici un exemple de dépendances de portée avec des conteneurs imbriqués utilisant l'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")
Dans l'exemple ci-dessus, l'MyResource imite une classe qui nécessite une configuration et un démontage, comme l'ouverture/la fermeture d'un fichier ou d'une base de données.
L'container fournit la ressource à l'aide de providers.Resource, et lorsque vous utilisez with container.resource(), elle entre et sort correctement du contexte.
Ce modèle garantit une gestion claire des ressources telles que les descripteurs de fichiers, les connexions à la base de données ou les sessions réseau.
Imaginez un point de terminaison API qui nécessite une session de base de données pour la requête en cours ou un enregistreur avec un contexte spécifique à la requête.
Avec les dépendances limitées, le framework crée un conteneur limité à la requête lorsque la requête commence à résoudre toutes les dépendances de cette portée.
À la fin de la requête, toutes les opérations de nettoyage nécessaires, telles que la fermeture des sessions de base de données, sont effectuées automatiquement. Cela garantit que les états ne sont pas partagés entre les requêtes et évite les fuites de ressources.
Injection asynchrone
Pour gérer efficacement les tâches liées aux E/S, une exécution asynchrone est nécessaire. Les systèmes d'injection de dépendances dans des frameworks tels que FastAPI et dependency-injector offrent une injection asynchrone à l'aide de async/await.
L'injection asynchrone désigne l'injection de dépendances qui sont des fonctions d'async def, ou la gestion d'async resources telles que les connexions asynchrones à une base de données, les API externes ou les tâches en arrière-plan.
Plutôt que de les résoudre de manière synchrone, le système d'injection de dépendances attend qu'elles soient résolues avant de procéder à l'injection.
Voici un exemple de dépendance asynchrone dans 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 gère automatiquement la résolution asynchrone de l'get_user et injecte le résultat.
dependency-injector prend également en charge la gestion asynchrone du cycle de vie à l'aide de fournisseurs d'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()
Dans le code ci-dessus :
-
resources.AsyncResourceest utilisé pour la logique de configuration/démontage asynchrone. -
await container.init_resources()initialise toutes les ressources et appelle l'init. -
await container.shutdown_resources()nettoie toutes les ressources. -
await container.db()obtient l'instance de ressources réelle, qui estAsyncDB.
L'utilisation de l'injection de dépendances asynchrones contribue à maintenir les performances de votre application en évitant les E/S synchrones dans les routes asynchrones et élimine le besoin d'attendre manuellement les dépendances dans votre logique.
Conséquences en matière de sécurité
Si l'injection de dépendances améliore la modularité et la testabilité, elle peut également introduire des risques potentiels pour la sécurité si elle n'est pas gérée avec soin.
Un risque important est la confusion des dépendances, où des paquets malveillants imitent des paquets légitimes. Par exemple, des attaquants ont téléchargé des paquets sur PyPI avec des noms utilisés dans les systèmes internes des entreprises afin de tromper les applications et les inciter à utiliser la version malveillante.
Pour garantir votre sécurité, veuillez vous assurer de ne jamais injecter de composants non fiables ou contrôlés par l'utilisateur et de valider les sources de configuration, telles que les variables d'environnement, avant leur injection.
Veuillez utiliser les fichiers « requirements.txt » ou « poetry.lock » pour verrouiller des versions précises et automatiser l'analyse de sécurité lors des modifications de dépendances. De plus, veuillez vous assurer de vérifier régulièrement vos dépendances afin de détecter toute vulnérabilité.
Il est également essentiel de centraliser le contrôle de vos dépendances, car cela garantit la cohérence entre vos applications et facilite l'application des correctifs de sécurité ou des mises à jour de version. Cela permet d'éviter les « dépendances cachées » introduites de différentes manières dans le code.
Plusieurs outils permettent de détecter les risques et d'améliorer votre posture de sécurité :
- Snyk: Recherche les vulnérabilités connues dans les paquets Python et propose des correctifs.
- Xygeni: Assure la protection des pipelines CI/CD.
- Sécurité: Vérification des paquets non sécurisés et des vulnérabilités du code.
- Bandit: Analyseur de code statique pour Python axé sur les problèmes de sécurité.
Applications concrètes
L'injection de dépendances n'est pas seulement un modèle théorique ; elle est utilisée dans divers systèmes de production pour gérer la complexité, améliorer la flexibilité et rationaliser les tests.
Configuration du service Web
Dans les applications web modernes, l'injection de dépendances joue un rôle crucial dans la gestion des services standard, tels que l'authentification, la journalisation et l'accès aux bases de données.
Les frameworks tels que FastAPI jouent un rôle essentiel dans la résolution des routes et des dépendances.
L'exemple ci-dessous illustre une implémentation d'authentification centralisée dans 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']}"}
Dans le code ci-dessus, get_current_user() est injecté en tant que dépendance de route, mais la logique de vérification du jeton est centralisée et réutilisable.
Vous pouvez également procéder de la même manière pour l'accès à la base de données, de sorte que les sessions de base de données soient automatiquement gérées et nettoyées.
Voici un exemple illustrant comment utiliser l'injection de dépendances pour centraliser l'accès à la base de données.
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")
Dans le code ci-dessus, l'get_db_session est un fournisseur de dépendances. Lorsque la route read_items est appelée, FastAPI injecte automatiquement le paramètre db, permettant à la route d'accéder à la base de données sans avoir à gérer manuellement la configuration ou la fermeture de la session.
Développement piloté par les tests
L'injection de dépendances vous permet de modifier facilement les dépendances des composants, ce qui est important lors des tests.
Vous pouvez remplacer les services réels par des simulacres pendant les tests,ce qui rend votre code plus modulaire et plus facile à tester.
FastAPI et l'dependency-injector nt la possibilité de remplacer les dépendances lors des tests.
Voici un exemple dans 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
Dans l'exemple de code ci-dessus, la méthode ` get_db_session ` est remplacée par une base de données fictive pendant les tests ; il n'est donc pas nécessaire de modifier le code de production.
De même, dans depedency-injector, vous pouvez remplacer votre base de données réelle par une base de données fictive à des fins de test, comme dans l'exemple ci-dessous.
from containers import Container
def test_service_behavior():
container = Container()
container.db.override(providers.Factory(TestDB))
service = container.service()
assert service.get_data() == "mocked"
La méthode d'override() remplace la dépendance réelle par une simulation, qui est propre, contrôlée et réversible.
Voici quelques stratégies à utiliser pour remplacer les dépendances :
- Injection du constructeur : Veuillez vous assurer de transmettre les mocks directement au constructeur.
- Remplacement des paramètres du framework : Veuillez utiliser les outils de remplacement intégrés tels que
FastAPI.dependency_overridesdans FastAPI oudependency_injector.override()dansdependency-injector. - Calendrier : Veuillez utiliser des frameworks de test tels que
pytestpour créer des simulacres réutilisables et les injecter via des fixtures.
Meilleures pratiques et anti-modèles
Bien que l'injection de dépendances vise à rendre le code modulaire et facile à tester, elle peut entraîner des bogues ou complexifier le code.
Pratiques recommandées
Voici quelques bonnes pratiques à respecter lors de la création de dépendances :
- Programmez des interfaces, pas des implémentations : Définissez vos dépendances à l'aide de classes de base abstraites ou d'interfaces plutôt que d'implémentations complexes. Cela facilite le test de votre code et encourage également l'utilisation de simulacres dans les tests unitaires.
- Privilégiez la composition à l'héritage : Plutôt que de créer des hiérarchies de classes complexes, composez des objets à l'aide de dépendances injectées afin de conserver des composants compacts et ciblés.
- Configuration centralisée des dépendances : Utilisez un seul conteneur ou module pour définir et gérer les dépendances, ce qui facilite la substitution et les tests.
- Évitez les dépendances circulaires : Concevez soigneusement votre graphe de dépendances afin d'éviter les références circulaires en utilisant des fonctions d'usine ou des fournisseurs pour retarder l'instanciation.
Erreurs courantes
Voici quelques pièges courants à éviter lors de l'utilisation de l'injection de dépendances.
- Anti-modèle du localisateur de services : Évitez d'utiliser un conteneur global pour récupérer dynamiquement des dépendances, car cela peut masquer des dépendances et rendre le code difficile à comprendre et à tester.
- Surcharge de gaz : Évitez d'injecter trop de services dans une seule classe ou fonction afin d'éviter que votre code ne donne lieu à des constructeurs trop lourds et à une logique difficile à tester.
- Mauvaise gestion du champ d'application et fuites au niveau de l'État : Veuillez vous assurer de toujours utiliser la portée appropriée afin d'éviter le partage d'état entre les utilisateurs ou les tests.
Test d'injection de dépendances en Python
L'injection de dépendances facilite l'injection d'objets fictifs dans vos composants, simplifiant ainsi les tests unitaires. Cela vous permet de séparer les composants fondamentaux de ceux spécifiques aux tests.
Grâce à l'injection de dépendances, les tests sont déterministes, n'ont aucun effet secondaire et s'exécutent plus rapidement.
Voici un exemple illustrant comment utiliser l'injection de dépendances pour injecter des versions fictives, simulées ou factices de ces dépendances dans vos tests.
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!")
Dans les tests, veuillez remplacer EmailService par une simulation :
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"
Veuillez vous assurer d'utiliser fréquemment l'injection de dépendances et d'éviter les modifications arbitraires afin de faciliter la maintenance et d'éviter des changements importants en cas de modification des noms internes.
Conclusion
Même si vous n'avez pas besoin de l'injection de dépendances au début pour les petits projets, vous pourrez bénéficier de la structure et de la modularité offertes par l'injection de dépendances à mesure que le projet prendra de l'ampleur et évoluera.
À l'avenir, l'injection de dépendances dans Python semble prometteuse, et nous pouvons nous attendre à ce qu'elle se poursuive avec un système de types amélioré et une gestion du cycle de vie asynchrone optimisée.
La maîtrise de l'injection de dépendances est essentielle pour devenir un développeur Python expérimenté. Cependant, cela ne s'arrête pas là ; de nombreux autres concepts avancés peuvent améliorer vos compétences en Python.
Pour vous aider à accélérer votre apprentissage, voici quelques-unes de nos ressources que nous vous recommandons de consulter.
Cours :
- Développement de paquets Python
- Introduction à PySpark
- Programmation orientée objet en Python
- Programmation orientée objet intermédiaire en Python
Articles de blog :
Instructeur expérimenté en science des données et biostatisticien avec une expertise en Python, R et apprentissage automatique.
Foire aux questions
Qu'est-ce que l'injection de dépendances ?
L'injection de dépendances est un modèle de conception qui vous permet d'écrire un code plus propre en créant des dépendances au sein d'une classe, qui peuvent être transmises depuis l'extérieur, ce qui facilite la gestion et la modification de votre code.
Quels sont les principaux avantages de l'injection de dépendances ?
L'injection de dépendances rend votre code modulaire, testable et facile à maintenir.
Comment puis-je implémenter l'injection de dépendances en Python ?
Vous pouvez implémenter l'injection de dépendances en Python manuellement ou à l'aide de bibliothèques telles que dependency_injector.
Qu'est-ce que l'injection asynchrone ?
L'injection asynchrone désigne l'injection de dépendances qui sont des fonctions d'async def, ou la gestion d'async resources telles que les connexions asynchrones à une base de données, les API externes ou les tâches en arrière-plan.
Quels sont les risques de sécurité liés à l'injection de dépendances et comment puis-je les gérer ?
Un risque important est la confusion des dépendances, où des paquets malveillants imitent des paquets légitimes. Pour éviter cela, vous pouvez utiliser des outils tels que Snyk, Xygeni, Safety ou Bandit afin d'analyser votre code et vos paquets à la recherche de vulnérabilités.
