Curso
La inyección de dependencias es un patrón de diseño en el que los objetos o servicios (nos referimos a las dependencias) se proporcionan a una clase desde el exterior, en lugar de que la clase los cree internamente. Esto desacopla los componentes.
El uso de la inyección de dependencias en Python tiene muchas ventajas, tales como:
- Modularidad: Tu código se divide en partes más pequeñas y reutilizables.
- Capacidad de prueba: Es más fácil probar tu código porque puedes sustituir la parte real por otras simuladas.
- Mantenibilidad: Puedes actualizar o modificar partes de tu código sin afectar al resto del sistema.
En este tutorial, te mostraré cómo utilizar la inyección de dependencias en Python a través de ejemplos sencillos y prácticos.
Comprender la inyección de dependencias en Python
Para comprender cómo implementar la inyección de dependencias, es esencial que comprendas los principios clave que la rigen.
La inyección de dependencias se basa en algo llamado principio de inversión de control (IoC), que significa que, en lugar de que una clase cree y gestione sus dependencias, las recibe de una fuente externa. Esto ayuda a centrarse en una sola tarea y hace que tu código sea más limpio.
La inyección de dependencias en Python incluye estas formas comunes, aunque hay otras:
- Inyección del constructor: Pasa las dependencias a través del constructor de la clase.
- Inyección del setter: Establece las dependencias utilizando un método setter después de crear el objeto.
- Inyección de método: Pasa las dependencias directamente al método que las necesita.
¿Por qué utilizar la inyección de dependencias en Python?
Como programador, comprender e implementar la inyección de dependencias puede marcar una gran diferencia en el diseño de tu código. Veamos algunas ventajas de la inyección de dependencias.
Modularidad, reutilización y flexibilidad
La inyección de dependencias te permite dividir el código en componentes más pequeños y específicos, lo que proporciona una mayor flexibilidad y modularidad.
Cada parte o componente se encarga de una tarea específica y depende de elementos externos, lo que facilita su reutilización en diferentes partes de tu aplicación o de la de otra persona.
Por ejemplo, si tienes una clase que envía mensajes. En lugar de integrar una lógica de correo electrónico o SMS directamente en tu servicio, puedes insertar una clase de remitente de correo electrónico o SMS.
Ahora, tu clase que envía mensajes no se preocupa por cómo se envía el mensaje, sino que simplemente utiliza el remitente que se le proporciona. Esto implica que puedes utilizar tu remitente de mensajes con diferentes remitentes de correo electrónico o SMS.
Mantenibilidad y capacidad de prueba
Al utilizar la inyección de dependencias, puedes sustituir fácilmente las dependencias reales por datos simulados durante las pruebas. Esto simplifica la escritura de pruebas unitarias sin depender de API o bases de datos.
Supongamos, como ejercicio de reflexión, que tienes una clase que guarda datos en una base de datos. A efectos de prueba, puedes inyectar una base de datos ficticia en lugar de una real para evitar que la prueba quede vinculada a la base de datos de producción.
La inyección de dependencias te permite modificar partes específicas de tu código sin romper todo el sistema. Esto resulta especialmente útil en situaciones en las que necesitas cambiar de proveedor de pagos en una aplicación, por ejemplo, de Stripe a PayPal, sin modificar el resto del código.
Inversión de control y dependencias intercambiables
La idea de la inversión de control es la siguiente: la inyección de dependencias trasladará la responsabilidad de crear y gestionar las dependencias a una fuente externa.
Esto implica que puedes cambiar fácilmente una implementación por otra; por ejemplo, si tienes una clase ReportGenerator que formatea y exporta informes.
En lugar de que la clase decida el formato de exportación, podrías insertar una clase exportadora, como PDFExporter, ExcelExporter o CSVExporter.
A la clase ` ReportGenerator ` no le importa cómo se realiza la exportación, por lo que para cambiar un formato, solo tienes que cambiar el exportador que inyectas, sin modificar en absoluto el ` ReportGenerator `.
Implementación de la inyección de dependencias en Python
Ahora que tienes una idea clara de qué es la inyección de dependencias y por qué es útil, veamos cómo puedes implementarla en Python. Comencemos con el método más sencillo: la inyección manual de dependencias.
Inyección manual de dependencias
En la inyección manual de dependencias, tú mismo creas las dependencias y las pasas a la clase o función que las necesita.
Este método funciona bien para aplicaciones pequeñas, pero a medida que tu aplicación crece, es necesario utilizar un marco de inyección de dependencias para evitar errores.
Aquí tienes un ejemplo de código sin inyección de dependencias.
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!")
En el ejemplo anterior, UserNotifier está estrechamente vinculado a EmailService. No es fácil intercambiar o simular EmailService para realizar pruebas.
Aquí hay otra versión, pero con inyección de dependencias manual.
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 versión anterior es flexible y te permite inyectar varios servicios de mensajería; también puedes sustituir EmailService por una simulación en las pruebas.
Uso del marco de inyección de dependencias
A medida que tu aplicación crece, gestionar las dependencias se vuelve complejo y complicado.
El dependency-injector marco proporciona un enfoque estructurado.
Arquitectura de proveedor de contenedores
El marco de trabajo de control de enrutamiento de eventos ( dependency-injector ) funciona basándose en una arquitectura de contenedor-proveedor.
- Contenedor: Este es el registro central para las dependencias de tu aplicación.
- Proveedores: Esto define cómo se crean tus dependencias.
- Configuración: Esto te permite modificar la configuración e insertarla en objetos.
- Anulación: Te permite sustituir dependencias sin cambiar el código de la aplicación, especialmente durante las pruebas.
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!")
En el ejemplo anterior:
-
Containerdefine cómo se crean las instancias -
Singletongarantiza que solo exista una instancia deEmailService -
Factorycrea un nuevo objeto de control de navegación (UserNotifier) con el control de navegación de la página principal (EmailService) inyectado automáticamente.
Mecanismo de cableado
Puedes utilizar decoradores como @inject para reducir la necesidad de pasar dependencias manualmente.
Aquí hay otra versión del ejemplo anterior:
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()
En la versión actual;
-
Sin añadir manualmente la dependencia, el decorador «
@inject» inyecta automáticamente la dependencia de la función o clase. -
La clase «
Provide» indica a «dependency-injector» qué dependencia debe inyectar.
Este enfoque elimina la necesidad de dependencias de cableado manual desde el contenedor.
Es útil en aplicaciones más grandes, donde los servicios pueden depender de varios componentes (de ahí la necesidad de inicializarlos automáticamente).
Uso de la inyección de dependencias en marcos populares de Python
La mayoría de los frameworks modernos de Python facilitan el trabajo con la inyección de dependencias, lo que te permite escribir código más limpio y fácil de probar.
En esta sección, aprenderás cómo se aplica la inyección de dependencias en Flask, Django y FastAPI.
Inyección de dependencias en Flask
Flask no tiene soporte integrado para la inyección de dependencias, pero puedes implementarla utilizando la biblioteca dependency-injector siguiendo estos pasos.
Paso 1: Define tus servicios
class GreetingService:
def get_greeting(self, name):
return f"Hello, {name}!"
Paso 2: Configura tu contenedor de inyección de dependencias
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
greeting_service = providers.Factory(GreetingService)
Paso 3: Crea la aplicación Flask e inyecta las dependencias.
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)
Del código anterior:
-
La función
name = request.args.get(“name”, “World”)extraenamede la cadena de consulta y, si no se proporciona, utiliza”World”como valor predeterminado. -
return greeting_service.get_greeting(name)llama al método en el servicio inyectado y devuelve un mensaje de saludo. -
container.wire(modules=[__name__])indica al inyector de dependencias que busque en este módulo decoradores de inyección de dependencias (@inject) y marcadores de inyección de dependencias (Provide[...]) para conectarlos a los proveedores reales.
Paso 4: Prueba la aplicación
Ejecuta la aplicación en tu terminal utilizando el comando python .py y visita la URL http://localhost:5000/greet?name=Jacob.
Deberías ver lo siguiente.

Inyección de dependencias en Django
Al igual que Flask, Django no ofrece inyección de dependencias integrada. Sin embargo, puedes utilizar sus vistas basadas en clases, middleware y estructura de aplicaciones para integrar la inyección de dependencias manualmente o utilizando la biblioteca dependency-injector.
A continuación se muestra un procedimiento paso a paso para implementar la inyección de dependencias en Django.
Paso 1: Define tu servicio
Crea un servicio sencillo que contenga la lógica de tu negocio en un nuevo archivo services.py dentro de la carpeta de tu aplicación.
# myapp/services.py
class GreetingService:
def get_greeting(self, name):
return f"Hello, {name}!"
Paso 2: Configurar el contenedor del inyector de dependencias
Dentro del directorio de la aplicación, crea un contenedor utilizando la biblioteca dependency-injector dentro de un nuevo archivo containers.py.
# myapp/containers.py
from dependency_injector import containers, providers
from .services import GreetingService
class Container(containers.DeclarativeContainer):
greeting_service = providers.Factory(GreetingService)
Paso 3: Crear una vista con dependencias inyectadas
Utiliza el decorador « @inject » en tu vista para recibir las dependencias en « 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)
Paso 4: Actualiza urls.py
En la carpeta del proyecto, ve al archivo urls.py y conecta el inyector de dependencias al sistema de enrutamiento de 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),
]
Paso 5: Conecta el contenedor en apps.py
Ve a apps.py y conecta el contenedor a la aplicación.
# 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"])
A continuación, ve a la configuración de la aplicación en INSTALLED_APPS y añade tu aplicación.
# settings.py
INSTALLED_APPS = [
'myapp.apps.MyAppConfig',
...
]
Paso 6: Probar la vista
Inicia tu servidor Django y accede a la vista a través de la URL http://localhost:8000/greet/?name=Django.
Deberías ver lo siguiente.

Inyección de dependencias en FastAPI
FastAPI tiene soporte integrado para la inyección de dependencias a través del marcador especial Depends(), que inyecta cualquier cosa que devuelva una función.
A continuación, se incluye una guía paso a paso sobre cómo realizar la inyección de dependencias en FastAPI.
Paso 1: Define un servicio
class GreetingService:
def get_greeting(self, name: str) -> str:
return f"Hello, {name}!"
Paso 2: Crear la función de dependencia
def get_greeting_service():
return GreetingService()
Paso 3: Configurar la aplicación FastAPI y utilizar la inyección de dependencias
app = FastAPI()
@app.get("/greet")
def greet(
name: str = "World", service: GreetingService = Depends(get_greeting_service)
):
return {"message": service.get_greeting(name)}
Paso 4: Ejecuta y prueba la aplicación.
Inicia tu servidor con el comando uvicorn main:app –reload y visita la URL http://localhost:8000/greet?name=FastAPI. Deberías ver la siguiente respuesta.

Comparación de marcos de inyección de dependencias en Python
Existen varios marcos de dependencias de Python, cada uno con sus propias ventajas, sintaxis y casos de uso. La elección del marco adecuado depende del tamaño y la estructura de tu proyecto.
A continuación se muestra una comparación de algunos marcos comunes de inyección de dependencias en Python.
|
Marco |
Mejor Para |
Puntos fuertes |
Limitaciones |
|
|
Aplicaciones complejas en uso productivo |
Completo, rápido y configurable |
Verboso |
|
Sencillo y adecuado para aplicaciones mínimas. |
Sintaxis limpia |
Menos avanzado que otros. |
|
|
Lógica de enlace avanzada |
Cableado automático |
Más lento debido a la introspección |
|
|
Aplicaciones sencillas |
Mínimo y rápido |
Funciones limitadas en comparación con otros |
|
|
Aplicaciones FastAPI |
Compatibilidad integrada con async y también se puede probar. |
No reutilizable fuera de FastAPI. |
|
|
Aplicaciones Flask |
Fácil integración con aplicaciones Flask |
Depende de |
|
|
Proyectos Django |
Proporciona inyección de dependencias para vistas y middleware. |
Depende de |
Patrones de implementación avanzados
A medida que el código base de tu aplicación se vuelve más grande, la inyección de dependencias se vuelve difícil de mantener.
Puedes utilizar la inyección de dependencias avanzada para gestionar tareas como los recursos del ámbito de la solicitud, la limpieza y la separación de preocupaciones.
Dependencias con ámbito
Las dependencias con ámbito te permiten controlar cuánto tiempo persiste una dependencia, por ejemplo, cuando deseas un nuevo objeto por solicitud, un objeto compartido durante toda la vida útil de la aplicación o la limpieza automática de recursos como las conexiones a bases de datos.
La mayoría de los marcos de inyección de dependencias admiten ámbitos como:
- Singleton: Una instancia para toda la aplicación.
- Fábrica: Una nueva instancia cada vez que se inyecta.
- Local en el hilo o en la solicitud: Una sola instancia por hilo/solicitud.
- Recurso: Gestionado con lógica de configuración y desmontaje.
A continuación se muestra un ejemplo de dependencias con ámbito y contenedores anidados utilizando 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")
En el ejemplo anterior, MyResource imita una clase que requiere configuración y desmontaje, como abrir/cerrar un archivo o una base de datos.
El controlador de recursos de clase única ( container ) proporciona el recurso mediante providers.Resource y, cuando utilizas with container.resource(), entra y sale del contexto correctamente.
Este patrón garantiza que los recursos, como los manejadores de archivos, las conexiones a bases de datos o las sesiones de red, se gestionen de forma limpia.
Imagina un punto final de API que necesita una sesión de base de datos para la solicitud actual o un registrador con contexto específico de la solicitud.
Con las dependencias del ámbito, el marco crea un contenedor del ámbito de la solicitud cuando la solicitud comienza a resolver todas las dependencias de ese ámbito.
Cuando finaliza la solicitud, se lleva a cabo automáticamente cualquier limpieza necesaria, como cerrar sesiones de la base de datos. Esto garantiza que los estados no se compartan entre solicitudes y evita fugas de recursos.
Inyección asíncrona
Para gestionar tareas vinculadas a E/S de forma eficiente, necesitas una ejecución asíncrona. Los sistemas de inyección de dependencias en marcos como FastAPI y dependency-injector ofrecen inyección asíncrona mediante async/await.
La inyección asíncrona se refiere a la inyección de dependencias que son funciones e async def es o a la gestión de e async resources es como conexiones de bases de datos asíncronas, API externas o tareas en segundo plano.
En lugar de resolverlas de forma sincrónica, el sistema de inyección de dependencias espera a que se resuelvan antes de inyectarlas.
El siguiente es un ejemplo de una dependencia asíncrona en 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 gestiona automáticamente la resolución asíncrona de get_user e inyecta el resultado.
dependency-injector También admite la gestión asíncrona del ciclo de vida mediante proveedores de eventos ( 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()
En el código anterior:
-
resources.AsyncResourceSe utiliza para la lógica de configuración/desmontaje asíncrono. -
await container.init_resources()inicializa todos los recursos y llama ainit. -
await container.shutdown_resources()limpia todos los recursos. -
await container.db()obtiene la instancia real de los recursos, que esAsyncDB.
El uso de la inyección de dependencias asíncronas ayuda a mantener el rendimiento de la aplicación al evitar la E/S sincrónica en rutas asíncronas y elimina la necesidad de esperar manualmente las dependencias en la lógica.
Implicaciones para la seguridad
Si bien la inyección de dependencias mejora la modularidad y la capacidad de prueba, puede introducir riesgos potenciales de seguridad si no se gestiona con cuidado.
Un riesgo importante es la confusión de dependencias, en la que paquetes maliciosos imitan a otros legítimos. Por ejemplo, los atacantes han subido paquetes a PyPI con nombres utilizados en sistemas empresariales internos para engañar a las aplicaciones y que utilicen la versión maliciosa.
Para mantener la seguridad, asegúrate de no inyectar nunca componentes que no sean de confianza o controlados por el usuario y valida las fuentes de configuración, como las variables de entorno, antes de inyectarlas.
Utiliza los archivos « requirements.txt » o « poetry.lock » para bloquear versiones exactas y automatizar el análisis de seguridad cuando se produzcan cambios en las dependencias. Además, asegúrate de auditar periódicamente tus dependencias en busca de vulnerabilidades.
Centralizar el control de tus dependencias también es esencial, ya que garantiza la coherencia entre tus aplicaciones y facilita la aplicación de parches de seguridad o actualizaciones de versiones. Ayuda a evitar las «dependencias ocultas» inyectadas de diferentes maneras en el código base.
Existen varias herramientas que pueden detectar riesgos y mejorar tu postura de seguridad:
- Snyk: Busca vulnerabilidades conocidas en paquetes Python y sugiere soluciones.
- Xygeni: Ofrece protección para los procesos de CI/CD.
- Seguridad: Comprueba si hay paquetes inseguros y vulnerabilidades en el código.
- Bandit: Un analizador de código estático para Python centrado en cuestiones de seguridad.
Aplicaciones en el mundo real
La inyección de dependencias no es solo un patrón teórico, sino que se utiliza en diversos sistemas de producción para gestionar la complejidad, mejorar la flexibilidad y optimizar las pruebas.
Configuración del servicio web
En las aplicaciones web modernas, la inyección de dependencias desempeña un papel crucial en la gestión de servicios estándar, como la autenticación, el registro y el acceso a bases de datos.
Los marcos como FastAPI desempeñan un papel esencial en la resolución de rutas y dependencias.
El siguiente ejemplo ilustra una implementación de autenticación centralizada en 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']}"}
En el código anterior, get_current_user() se inyecta como una dependencia de ruta, pero la lógica para verificar el token está centralizada y es reutilizable.
También puedes hacer lo mismo para el acceso a la base de datos, de modo que las sesiones de la base de datos se gestionen y limpien automáticamente.
A continuación se muestra un ejemplo de cómo puedes utilizar la inyección de dependencias para centralizar el acceso a la base de datos.
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")
En el código anterior, get_db_session es un proveedor de dependencias. Cuando se llama a la ruta read_items, FastAPI inyecta automáticamente el parámetro db, lo que permite a la ruta acceder a la base de datos sin tener que gestionar manualmente la configuración o el cierre de la sesión.
Desarrollo basado en pruebas
La inyección de dependencias facilita el cambio de las dependencias que reciben los componentes, lo cual es importante a la hora de realizar pruebas.
Puedes sustituir servicios reales por simulaciones durante las pruebas,lo que hace que tu código sea más modular y fácil de probar.
FastAPI y dependency-injector admiten la sustitución de dependencias en tiempo de prueba.
Aquí tienes un ejemplo en 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
En el ejemplo de código anterior, get_db_session se sobrescribe con una base de datos simulada durante las pruebas; por lo tanto, no es necesario parchear ni modificar el código de producción.
Lo mismo ocurre en depedency-injector, donde puedes sustituir tu base de datos real por una base de datos ficticia con fines de prueba, como en el ejemplo siguiente.
from containers import Container
def test_service_behavior():
container = Container()
container.db.override(providers.Factory(TestDB))
service = container.service()
assert service.get_data() == "mocked"
El método « override() » sustituye la dependencia real por una simulación, que es limpia, controlada y reversible.
A continuación, se indican algunas estrategias que debes utilizar para anular las dependencias:
- Inyección del constructor: Asegúrate de pasar las simulaciones directamente al constructor.
- Anulaciones del marco: Utiliza herramientas de anulación integradas, como
FastAPI.dependency_overridesen FastAPI odependency_injector.override()endependency-injector. - Partidos: Utiliza marcos de pruebas como
pytestpara crear simulaciones reutilizables e insertarlas a través de accesorios.
Mejores prácticas y antipatrones
Aunque la inyección de dependencias tiene como objetivo hacer que el código sea modular y fácil de probar, puede provocar errores o crear complejidad en el código.
Prácticas recomendadas
A continuación, se indican algunas prácticas recomendadas que debes seguir al crear dependencias:
- Programa para interfaces, no para implementaciones: Define tus dependencias utilizando clases base abstractas o interfaces en lugar de implementaciones complejas. Esto facilita la comprobación del código y fomenta el uso de simulaciones en las pruebas unitarias.
- Preferimos la composición a la herencia: En lugar de crear jerarquías de clases complejas, compón objetos utilizando dependencias inyectadas para mantener los componentes pequeños y centrados.
- Configuración centralizada de dependencias: Utiliza un único contenedor o módulo para definir y gestionar las dependencias, lo que facilita la sustitución y las pruebas.
- Evita las dependencias circulares: Diseña cuidadosamente tu gráfico de dependencias para evitar referencias circulares utilizando funciones de fábrica o proveedores para retrasar la instanciación.
Errores comunes
A continuación, se indican algunos errores comunes que debes evitar al utilizar la inyección de dependencias.
- Antipatrón del localizador de servicios: Evita utilizar un contenedor global para obtener dependencias de forma dinámica, ya que esto puede ocultar dependencias y complicar la comprensión y las pruebas del código.
- Sobredosificación: Evita inyectar demasiados servicios en una sola clase o función para evitar que tu código dé lugar a constructores inflados y a una lógica difícil de probar.
- Mala gestión del alcance y fugas estatales: Asegúrate de utilizar siempre el ámbito adecuado para evitar compartir el estado entre usuarios o pruebas.
Pruebas de inyección de dependencias en Python
La inyección de dependencias facilita la inserción de objetos simulados en tus componentes, lo que simplifica las pruebas unitarias. Esto te permite separar los componentes fundamentales de los específicos de la prueba.
Con la inyección de dependencias, las pruebas son deterministas, no tienen efectos secundarios y se ejecutan más rápidamente.
A continuación se muestra un ejemplo de cómo puedes utilizar la inyección de dependencias para inyectar versiones simuladas, stubs o falsas de esas dependencias en tus pruebas.
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!")
En las pruebas, sustituye EmailService por una simulación:
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"
Asegúrate de utilizar con frecuencia la inyección de dependencias y evita los parches improvisados para facilitar el mantenimiento y evitar cambios importantes cuando se modifiquen los nombres internos.
Conclusión
Aunque es posible que al principio no necesites la inyección de dependencias para proyectos pequeños, a medida que el proyecto crezca y se amplíe, podrás beneficiarte de la estructura y la modularidad que ofrece la inyección de dependencias.
De cara al futuro, la inyección de dependencias en Python es prometedora, y podemos esperar que continúe con un sistema de tipos mejorado y una gestión del ciclo de vida asíncrono optimizada.
Dominar la inyección de dependencias es esencial para convertirse en un programador Python fluido. Pero eso no es todo: hay muchos otros conceptos avanzados que pueden mejorar tus habilidades en Python.
Para ayudarte a acelerar tu proceso de aprendizaje, aquí tienes algunos de nuestros recursos que te recomendamos consultar.
Cursos:
- Desarrollo de paquetes Python
- Introducción a PySpark
- Programación orientada a objetos en Python
- Programación orientada a objetos intermedia en Python
Entradas del blog:
Instructor experimentado en ciencia de datos y bioestadístico con experiencia en Python, R y aprendizaje automático.
Preguntas frecuentes
¿Qué es la inyección de dependencias?
La inyección de dependencias es un patrón de diseño que te permite escribir código más limpio mediante la creación de dependencias dentro de una clase, que pueden pasarse desde el exterior, lo que facilita la gestión y modificación del código.
¿Cuáles son las principales ventajas de la inyección de dependencias?
La inyección de dependencias hace que tu código sea modular, comprobable y fácil de mantener.
¿Cómo puedo implementar la inyección de dependencias en Python?
Puedes implementar la inyección de dependencias en Python de forma manual o utilizando bibliotecas como dependency_injector.
¿Qué es la inyección asíncrona?
La inyección asíncrona se refiere a la inyección de dependencias que son funciones e async def es o a la gestión de e async resources es como conexiones de bases de datos asíncronas, API externas o tareas en segundo plano.
¿Cuáles son los riesgos de seguridad de la inyección de dependencias y cómo puedo gestionarlos?
Un riesgo importante es la confusión de dependencias, en la que paquetes maliciosos imitan a otros legítimos. Para evitarlo, puedes utilizar herramientas como Snyk, Xygeni, Safety o Bandit para analizar tu código y tus paquetes en busca de vulnerabilidades.


