Skip to main content

Inyección de dependencias en Python

Escrito por
Headshot of Lucien Chemaly

Lucien Chemaly

feature python linting

31 de octubre de 2023

0 minutos de lectura

La inyección de dependencias (DI) puede sonar compleja, pero en realidad se trata de mejorar la flexibilidad de tu código. Es como darle a tu software un impulso para que sea más adaptable y sólido. Y como Python es un lenguaje de programación muy popular, entender la inyección de dependencias es especialmente valioso. Imagina crear software que no solo sea bueno, sino excelente: fácil de ajustar, ampliar y proteger. Esa es la esencia de la DI en Python.

Si buscas cómo agregar la inyección de dependencias a tu trabajo, llegaste al lugar indicado. En este tutorial aprenderás cómo funciona la DI, cómo usarla y cómo puede mejorar tus proyectos de Python.

Cómo funciona la inyección de dependencias

Para entender mejor cómo funciona la DI, piensa en cómo se ensambla un auto. No tendría mucho sentido que cada auto tuviera que fabricar sus propias llantas, ¿verdad? En una fábrica de autos, las llantas se fabrican por separado y luego se instalan en el auto. Así, si un auto necesita otras llantas, se pueden cambiar. La DI aplica esta misma idea al mundo del software.

Imagina que estás explorando una aplicación de Python. Dentro hay una clase llamada DatabaseClient que necesita información específica para conectarse a una base de datos. Si incluyeras esta información directamente en la clase, sería como tener un auto con llantas que no se pueden cambiar. Pero con la DI, en lugar de dejar fija esa información, la proporcionas (o inyectas) desde el exterior. Así, si más adelante quieres cambiar los datos o probar otra base de datos, puedes hacerlo sin desarmar toda la aplicación.

Este enfoque ofrece muchos beneficios. Es como tener una caja de herramientas en la que puedes cambiar fácilmente las herramientas según el trabajo. Tu software se vuelve más flexible; puedes experimentar y hacer pruebas con facilidad, además de implementar actualizaciones o correcciones.

A continuación, revisa algunos beneficios de la inyección de dependencias para entender mejor por qué la necesitas.

Por qué necesitas la inyección de dependencias

La DI te da más control y facilita tu trabajo de programación. Descubre algunas razones clave para considerar incorporarla en tus proyectos.

Mejora la mantenibilidad y la capacidad de prueba del código

La inyección de dependencias es un patrón que promueve el desacoplamiento de los componentes y hace que dependan menos unos de otros. Esto significa que, si necesitas actualizar o modificar un componente o servicio en particular, puedes hacerlo sin causar un efecto dominó en toda la base de código.

Además, la DI es ideal para las pruebas. Cuando los componentes están desacoplados, es mucho más fácil inyectar dependencias simuladas durante las pruebas unitarias. Así puedes probar el comportamiento de partes individuales de forma aislada y realizar pruebas más completas y sólidas.

Permite cambiar las dependencias fácilmente

¿Alguna vez te enfrentaste a la difícil tarea de cambiar de tecnología, como migrar de una solución de base de datos a otra? Con la DI, estas transiciones son mucho más sencillas. En lugar de modificar toda la clase o el módulo que interactúa con la base de datos, solo tienes que cambiar la dependencia inyectada. Esta flexibilidad puede ahorrarte mucho tiempo y reducir la posibilidad de errores durante las migraciones o actualizaciones.

Mejora la modularidad y la reutilización del código

La inyección de dependencias fomenta el diseño de componentes que no están estrictamente vinculados a implementaciones específicas. Para ti, esto se traduce en una mayor modularidad de la base de código. Los módulos o componentes diseñados con la DI en mente se pueden reutilizar fácilmente en distintas partes de tu aplicación o incluso en proyectos completamente independientes. Esto puede acelerar el desarrollo y fomentar un enfoque de programación más uniforme.

Admite la inversión de control

En la programación tradicional, las clases suelen crear y administrar sus propias dependencias. Con la DI, este control se invierte. En lugar de que la clase sea responsable, entidades externas (a menudo un contenedor o framework de DI) se encargan de crear y proporcionar estas dependencias. Esta inversión de control (IoC) puede simplificar la administración de dependencias y promover una arquitectura más limpia y organizada.

Facilita la configuración del comportamiento de la aplicación

Una de las ventajas sutiles, pero poderosas, de la DI es su capacidad para cambiar el comportamiento de la aplicación sobre la marcha. Al inyectar distintas implementaciones de una dependencia, puedes modificar el funcionamiento de ciertas partes de la aplicación sin reescribir grandes cantidades de código. Ya sea que te adaptes a distintos entornos de ejecución o respondas a las preferencias de varios usuarios, la DI te da la flexibilidad para adaptar el comportamiento de la aplicación.

Limitaciones de la inyección de dependencias en Python

Aunque la DI ofrece muchos beneficios, es importante reconocer que también presenta desafíos y limitaciones. Ten en cuenta estas limitaciones al decidir si quieres incorporarla en tus proyectos de Python:

  • Complejidad: La DI cambia de manera fundamental cómo se crean y administran los componentes u objetos de una aplicación. En una aplicación extensa y de gran escala, configurar la DI requiere asignar cuidadosamente las dependencias, las interfaces y las clases que las implementan. Esta capa adicional puede implicar más código repetitivo, más configuración y una curva de aprendizaje más pronunciada, sobre todo si estás adaptando un proyecto existente para usar DI. Para hacerlo correctamente, necesitas comprender a fondo la arquitectura de tu aplicación.

  • Posible confusión: La DI puede ser abstracta. Si tú o tus compañeros de equipo no conocen bien la DI, es posible que algunas partes de la base de código les resulten confusas. En lugar de crear los objetos directamente, estos se proporcionan o inyectan, lo que al principio puede dificultar el seguimiento del flujo de una aplicación. Esto puede prolongar la incorporación de nuevos desarrolladores o aumentar la curva de aprendizaje.

  • Errores en tiempo de ejecución: Los frameworks de DI suelen conectar las dependencias en tiempo de ejecución. Si hay una incompatibilidad o falta una configuración, la aplicación puede fallar o comportarse de forma inesperada. Estos errores podrían no detectarse durante la compilación o las pruebas iniciales, por lo que puede ser más difícil diagnosticarlos y corregirlos. Las pruebas adecuadas y el conocimiento de la DI pueden ayudar a evitarlo, pero sigue siendo un riesgo potencial.

  • Sobrecarga de rendimiento: Aunque en muchas aplicaciones esta sobrecarga suele ser insignificante, resolver e inyectar dependencias en tiempo de ejecución puede afectar el rendimiento. Esto es especialmente relevante en situaciones en las que cada milisegundo cuenta, como las plataformas de trading de alta frecuencia. Es fundamental evaluar y medir el rendimiento de tu aplicación para comprender esta sobrecarga y optimizarla cuando sea necesario.

  • Dificultades para revertir cambios: Como las dependencias de la DI están estrechamente vinculadas, no siempre es sencillo revertir un cambio que causa problemas. Las dependencias pueden generar efectos en cadena, lo que significa que un cambio en un área puede afectar a muchos otros componentes. Por eso, las pruebas exhaustivas, el control de versiones y la documentación detallada son aún más importantes.

  • No siempre es necesaria: La DI resulta muy útil en aplicaciones complejas cuyos componentes deben tener un acoplamiento débil y probarse de forma aislada, y en las que se necesita una mayor modularidad. Sin embargo, en un script simple o una aplicación pequeña con pocos componentes, incorporar DI podría ser excesivo. Podría agregar complejidad sin aportar beneficios concretos. Como ocurre con cualquier patrón de diseño o decisión arquitectónica, es importante evaluar si es adecuado para la tarea.

Inyección de dependencias en frameworks populares de Python

En esta sección aprenderás a configurar la inyección de dependencias con tres frameworks populares de Python: Flask, Django y FastAPI. Aunque cada framework tiene su propio enfoque, todos comparten un principio fundamental: desacoplar las dependencias para crear aplicaciones más fáciles de mantener, modulares y sencillas de probar.

Antes de explorar los frameworks, asegúrate de tener Python instalado. Si aún no lo tienes, puedes descargarlo e instalarlo desde el sitio web oficial de Python.

Ahora que tienes Python en tu equipo, explora la inyección de dependencias en estos distintos frameworks.

Inyección de dependencias en Flask

Flask es un framework web ligero. Aunque no ofrece compatibilidad integrada con la DI, puedes integrar extensiones como Flask-Injector para implementarla.

Para instalar Flask y Flask-Injector, usa el siguiente comando:

pip3 install Flask Flask-Injector

Flask-Injector integra sin problemas la biblioteca Injector para controlar la inyección de dependencias en aplicaciones de Flask.

Crea una aplicación de Flask

Para crear una aplicación simple de Flask, crea una carpeta llamada flask_di_project. Luego, dentro de esta carpeta, crea un archivo nuevo llamado app.py y agrega el siguiente código:

from flask import Flask, jsonify
from flask_injector import FlaskInjector, inject, singleton

app = Flask(__name__)

class Database:
    def __init__(self):
        self.data = {"message": "Data from the Database!"}

class Logger:
    def log(self, message):
        print("Logging:", message)

class DataService:
    def __init__(self, database: Database, logger: Logger):
        self.database = database
        self.logger = logger

    def fetch_data(self):
        self.logger.log("Fetching data...")
        return self.database.data

@app.route('/')
@inject
def index(data_service: DataService):
    return jsonify(data_service.fetch_data())

# Dependency configurations
def configure(binder):
    binder.bind(Database, to=Database(), scope=singleton)
    binder.bind(Logger, to=Logger(), scope=singleton)
    binder.bind(DataService, to=DataService(Database(), Logger()), scope=singleton)

if __name__ == '__main__':
    FlaskInjector(app=app, modules=[configure])
    app.run(debug=True)

En este código, creas una aplicación web de Flask que utiliza la inyección de dependencias para administrar sus componentes. La aplicación consta de tres clases: Database, Logger y DataService. Defines las dependencias de la clase DataService mediante la inyección por constructor, lo que te permite inyectar instancias de Database y Logger al crear una instancia de DataService. Con el decorador @inject en la función de ruta index, usas Flask-Injector para inyectar automáticamente una instancia de DataService en la función. Además, una función configure define la asociación de estas dependencias para que Flask-Injector sepa cómo proporcionarlas cuando se necesite una instancia.

Ejecuta y prueba la aplicación

En la terminal o shell, ve al directorio que contiene app.py y ejecuta lo siguiente:

python3 app.py

Abre un navegador y ve a http://127.0.0.1:5000/. Deberías ver el mensaje {"message": "Data from the Database!"}:

Ventana del navegador que muestra una respuesta JSON con el mensaje “¡Datos de la base de datos!”

Inyección de dependencias en Django

Django, un framework web de alto nivel, ofrece opciones para usar DI mediante bibliotecas externas. En este caso, usarás la biblioteca Django Injector para incorporar la DI a tu proyecto de Django.

Usa el siguiente comando para instalar Django y Django Injector:

pip3 install Django django-injector

Crea un nuevo proyecto y una aplicación de Django

Para crear un nuevo proyecto y una aplicación de Django, usa los siguientes comandos:

django-admin startproject django_di_project
cd django_di_project
python3 manage.py startapp myapp

Una vez que hayas instalado django-injector, debes agregarlo a tu proyecto. Ve al archivo settings.py, ubicado dentro de la carpeta django_di_project, y agrega django_injector al arreglo INSTALLED_APPS:

INSTALLED_APPS = [
    … code omitted …
    'django_injector',
]

Luego, en myapp/views.py, usa django-injector para implementar la DI:

from django.http import JsonResponse
from injector import inject
from django.views import View
from myapp.services import DataService  # Import your DataService class

class MyView(View):
    @inject
    def __init__(self, data_service: DataService):
        self.data_service = data_service
        super().__init__()

    def get(self, request):
        data = self.data_service.fetch_data()
        return JsonResponse(data)

Aquí defines una vista de Django, MyView, que, cuando recibe una solicitud GET, obtiene datos mediante DataService, cuya dependencia se inyecta, y los devuelve como respuesta JSON.

Actualiza las URL

Actualiza django_di_project/urls.py para dirigir la URL raíz a esta vista:

from django.urls import path
from myapp.views import MyView

urlpatterns = [
    path('', MyView.as_view(), name='index'),
]

Luego, crea un archivo services.py en el directorio myapp y define allí la clase DataService:

from injector import inject

class DataService:
    @inject
    def __init__(self):
        self.message = "Hello from Dynamic DataService in Django!"

    def fetch_data(self):
        return {"message": self.message}

Al incorporar django-injector, integraste sin problemas la DI en tu aplicación de Django. El decorador @inject inyecta de forma eficiente la instancia de DataService en el constructor de la clase MyView, lo que promueve la modularidad y facilita la administración de tu aplicación de Django.

Ejecuta y prueba la aplicación

En la terminal o shell, dentro del directorio django_di_project, ejecuta lo siguiente:

python3 manage.py runserver

Abre el navegador y ve a http://127.0.0.1:8000/. Deberías ver el mensaje {"message": "Hello from Dynamic DataService in Django!"}:

Navegador que muestra una respuesta JSON: “¡Hola desde Dynamic DataService en Django!” en localhost:8000

Inyección de dependencias en FastAPI

FastAPI es un framework web moderno que incluye compatibilidad nativa con la DI. Al usar FastAPI, eliges un framework que integra la DI de forma nativa, lo que te permite crear aplicaciones modulares y fáciles de probar sin depender de bibliotecas externas.

Usa el siguiente código para instalar FastAPI y Uvicorn (un servidor ASGI para ejecutar FastAPI):

pip3 install fastapi uvicorn

Crea una aplicación sencilla de FastAPI

Para crear una aplicación sencilla con FastAPI, crea una carpeta nueva llamada fast_di_project en el directorio raíz. Dentro de esta carpeta, crea un archivo llamado main.py y agrega el siguiente código:

from fastapi import FastAPI, Depends

app = FastAPI()

class DataService:
    def fetch_data(self):
        return {"message": "Greetings from the Dynamic DataService in the World of FastAPI!"}

def get_data_service():
    return DataService()

@app.get('/')
def index(data_service: DataService = Depends(get_data_service)):
    data = data_service.fetch_data()
    return {"result": data, "info": "Fetched from the Dynamic DataService in FastAPI!"}

Aquí, empiezas por crear una instancia de la clase FastAPI. Luego, defines una clase DataService que encapsula un método para obtener datos. Cuando accedes a la URL raíz de tu aplicación, entra en acción la administración de dependencias integrada de FastAPI. Creaste una función get_data_service() para proporcionar una instancia de la clase DataService. El mecanismo Depends de FastAPI te permite inyectar esta instancia en la función de ruta index. Cuando visitas la URL raíz en tu navegador, recibes una respuesta que contiene los datos obtenidos junto con un mensaje personalizado de DataService, que es dinámico y forma parte del universo de FastAPI.

Ejecuta y prueba la aplicación

En tu terminal o shell, navega hasta el directorio que contiene main.py y ejecuta lo siguiente:

uvicorn main:app --reload

Abre el navegador y ve a http://127.0.0.1:8000/. Deberías ver el mensaje {"result":{"message":"Greetings from the Dynamic DataService in the World of FastAPI!"},"info":"Fetched from the Dynamic DataService in FastAPI!"}:

Navegador que muestra una respuesta de localhost de FastAPI con saludos JSON de Dynamic DataService.

Todo el código que se usa en este tutorial está disponible en este repositorio de GitHub.

Frameworks de inyección de dependencias para Python

Al decidir qué framework de DI usar, es importante evaluar los requisitos específicos de tu proyecto, el nivel de modularidad y facilidad de prueba que buscas, y el framework con el que trabajas. Cada opción ofrece distintas capacidades, y tu elección debe ajustarse a la complejidad y los objetivos de diseño de tu proyecto. Estas son algunas opciones:

Flask-Injector

Flask-Injector es una excelente opción para crear aplicaciones con Flask. Es especialmente útil para proyectos ligeros basados en este framework.

Flask-Injector se integra a la perfección con el ecosistema de Flask, lo que facilita administrar dependencias y lograr modularidad en tu aplicación Flask. Sin embargo, para aplicaciones más grandes y complejas que podrían requerir funciones más avanzadas de administración de dependencias, puedes explorar otros frameworks de DI con capacidades adicionales adaptadas a esas necesidades específicas.

Django Injector

Si desarrollas con Django, la biblioteca Django Injector puede ser un recurso valioso. Simplifica la DI en los proyectos de Django, por lo que es una excelente opción para lograr modularidad y facilidad de prueba.

Considera usar Django Injector para crear aplicaciones con Django que requieran una integración fluida de DI, especialmente en vistas basadas en clases, middleware y comandos de administración.

Depends

La función Depends de FastAPI está diseñada para funcionar con el framework asyncio de Python y ofrece funciones de DI para código asíncrono. Si creas aplicaciones asíncronas con FastAPI, Depends puede ayudarte a administrar e inyectar dependencias en tus funciones asíncronas y corrutinas. Esta función es especialmente valiosa al trabajar en proyectos basados en async con FastAPI, ya que ayuda a mantener la calidad del código y un diseño modular.

Dependency Injector

La biblioteca Dependency Injector es un framework de DI completo, apto para diversas aplicaciones de Python. Ofrece funciones avanzadas, como administración de contenedores, ámbitos y control del ciclo de vida.

Usa Dependency Injector en proyectos más grandes que requieran una administración sólida de dependencias, inversión de control y escenarios complejos en los que necesites más control sobre la forma de inyectar y administrar las dependencias.

Injector

La biblioteca Injector es un framework de DI de propósito general que se puede integrar en distintos proyectos de Python, sin importar el framework. Es especialmente adecuada para proyectos en los que quieres configurar y administrar manualmente la inyección de dependencias, sin integraciones específicas para cada framework.

Usa Injector cuando necesites una solución de DI flexible y versátil para aplicaciones que abarquen varios frameworks o incluso para scripts independientes.

Protege las dependencias de tu proyecto con Snyk

Al enfocarte en la funcionalidad, no puedes dejar la seguridad de lado. Aquí es donde Snyk puede ayudarte. Con el conjunto de herramientas de Snyk, puedes hacer lo siguiente:

  • Analizar las dependencias de Python para detectar vulnerabilidades y recibir sugerencias para corregirlas.

  • Recibir comentarios en tiempo real sobre problemas de seguridad mientras programas.

  • Aprovechar soluciones automatizadas para las vulnerabilidades detectadas.

  • Integrarte fácilmente con los pipelines de integración y entrega continuas (CI/CD) más populares para garantizar comprobaciones de seguridad constantes.

Al incorporar las extensiones de Snyk para IDE en tu flujo de trabajo, adoptas prácticas de programación segura de forma proactiva desde el principio y simplificas el proceso de administración de dependencias. Para obtener más información sobre Snyk, consulta la documentación oficial de Snyk.

Conclusión

En este artículo, aprendiste por qué necesitas la inyección de dependencias en Python. También conociste algunos de sus beneficios, como una mejor mantenibilidad y modularidad del código. Además, aprendiste sobre algunos de los desafíos que presenta, entre ellos la posibilidad de una mayor complejidad y de errores en tiempo de ejecución. También aprendiste a integrar la inyección de dependencias en frameworks de Python ampliamente reconocidos, como Flask, FastAPI y Django.

Aunque la DI puede presentar ciertos desafíos, dominar sus particularidades puede elevar la calidad de tus aplicaciones de Python. A medida que exploras la administración de dependencias, es fundamental aprovechar herramientas como Snyk para mantener la seguridad como prioridad en tus proyectos de desarrollo. Para conocer más sobre la administración de dependencias en Python y las prácticas recomendadas, te recomendamos explorar el administrador de paquetes Python Poetry en el blog de Snyk. Este recurso ofrece una descripción completa de las herramientas que pueden ayudarte a administrar tus proyectos de Python de forma más eficiente.

Empieza con los desafíos de Capture the Flag

Aprende a resolver desafíos de captura la bandera con nuestro taller virtual introductorio a pedido.