Introducción
Para Java, Dependency Injection funciona como POO puro, es decir, usted proporciona una interfaz para ser implementada y en su código marco acepta una instancia de una clase que implementa la interfaz definida.
Ahora, para Python, puedes hacer lo mismo, pero creo que ese método era demasiado general en el caso de Python. Entonces, ¿cómo lo implementaría de la manera Pythonic?
Caso de uso
Digamos que este es el código del marco:
class FrameworkClass():
def __init__(self, ...):
...
def do_the_job(self, ...):
# some stuff
# depending on some external function
El enfoque básico
La forma más ingenua (¿y quizás la mejor?) Es exigir que la función externa se proporcione en el FrameworkClass
constructor y luego se invoque desde el do_the_job
método.
Código marco:
class FrameworkClass():
def __init__(self, func):
self.func = func
def do_the_job(self, ...):
# some stuff
self.func(...)
Codigo del cliente:
def my_func():
# my implementation
framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)
Pregunta
La pregunta es breve. ¿Existe alguna forma Pythonic mejor utilizada para hacer esto? ¿O tal vez alguna biblioteca que admita dicha funcionalidad?
ACTUALIZACIÓN: Situación concreta
Imagine que desarrollo un marco de micro web, que maneja la autenticación mediante tokens. Este marco necesita una función para proporcionar algunos ID
obtenidos del token y obtener el usuario correspondiente ID
.
Obviamente, el marco no sabe nada sobre los usuarios ni sobre ninguna otra lógica específica de la aplicación, por lo que el código del cliente debe inyectar la funcionalidad del getter del usuario en el marco para que la autenticación funcione.
AttributeError
o deTypeError
otra manera), pero por lo demás es lo mismo.abs
laABCMeta
metaclase de 'con@abstractmethod
decorador y sin validación manual. Solo quiero obtener un par de opciones y sugerencias. El que citó es el más limpio, pero creo que con más gastos generales.Respuestas:
Mira a Raymond Hettinger - ¡Súper considerado súper! - PyCon 2015 para un argumento sobre cómo usar la herencia super y múltiple en lugar de DI. Si no tiene tiempo para ver el video completo, salte al minuto 15 (pero recomiendo verlo todo).
Aquí hay un ejemplo de cómo aplicar lo que se describe en este video a su ejemplo:
Código marco:
class TokenInterface(): def getUserFromToken(self, token): raise NotImplementedError class FrameworkClass(TokenInterface): def do_the_job(self, ...): # some stuff self.user = super().getUserFromToken(...)
Codigo del cliente:
class SQLUserFromToken(TokenInterface): def getUserFromToken(self, token): # load the user from the database return user class ClientFrameworkClass(FrameworkClass, SQLUserFromToken): pass framework_instance = ClientFrameworkClass() framework_instance.do_the_job(...)
Esto funcionará porque Python MRO garantizará que se llame al método de cliente getUserFromToken (si se usa super ()). El código tendrá que cambiar si está en Python 2.x.
Un beneficio adicional aquí es que esto generará una excepción si el cliente no proporciona una implementación.
Por supuesto, esto no es realmente una inyección de dependencia, es herencia múltiple y mixins, pero es una forma Pythonic de resolver su problema.
fuente
super()
:)La forma en que hacemos la inyección de dependencias en nuestro proyecto es mediante el uso de inject lib. Consulte la documentación . Recomiendo encarecidamente usarlo para DI. No tiene sentido con una sola función, pero comienza a tener mucho sentido cuando tiene que administrar múltiples fuentes de datos, etc., etc.
Siguiendo tu ejemplo, podría ser algo similar a:
# framework.py class FrameworkClass(): def __init__(self, func): self.func = func def do_the_job(self): # some stuff self.func()
Tu función personalizada:
# my_stuff.py def my_func(): print('aww yiss')
En algún lugar de la aplicación, desea crear un archivo de arranque que realiza un seguimiento de todas las dependencias definidas:
# bootstrap.py import inject from .my_stuff import my_func def configure_injection(binder): binder.bind(FrameworkClass, FrameworkClass(my_func)) inject.configure(configure_injection)
Y luego podrías consumir el código de esta manera:
# some_module.py (has to be loaded with bootstrap.py already loaded somewhere in your app) import inject from .framework import FrameworkClass framework_instance = inject.instance(FrameworkClass) framework_instance.do_the_job()
Me temo que esto es lo más pitónico posible (el módulo tiene algo de dulzura de Python como decoradores para inyectar por parámetro, etc.), ya que Python no tiene cosas sofisticadas como interfaces o sugerencias de tipo.
Entonces, responder directamente a su pregunta sería muy difícil. Creo que la verdadera pregunta es: ¿Python tiene algún soporte nativo para DI? Y la respuesta es, lamentablemente: no.
fuente
Hace algún tiempo escribí un microframework de inyección de dependencia con la ambición de convertirlo en Pythonic - Dependency Injector . Así es como puede verse su código en caso de su uso:
"""Example of dependency injection in Python.""" import logging import sqlite3 import boto.s3.connection import example.main import example.services import dependency_injector.containers as containers import dependency_injector.providers as providers class Platform(containers.DeclarativeContainer): """IoC container of platform service providers.""" logger = providers.Singleton(logging.Logger, name='example') database = providers.Singleton(sqlite3.connect, ':memory:') s3 = providers.Singleton(boto.s3.connection.S3Connection, aws_access_key_id='KEY', aws_secret_access_key='SECRET') class Services(containers.DeclarativeContainer): """IoC container of business service providers.""" users = providers.Factory(example.services.UsersService, logger=Platform.logger, db=Platform.database) auth = providers.Factory(example.services.AuthService, logger=Platform.logger, db=Platform.database, token_ttl=3600) photos = providers.Factory(example.services.PhotosService, logger=Platform.logger, db=Platform.database, s3=Platform.s3) class Application(containers.DeclarativeContainer): """IoC container of application component providers.""" main = providers.Callable(example.main.main, users_service=Services.users, auth_service=Services.auth, photos_service=Services.photos)
Aquí hay un enlace a una descripción más extensa de este ejemplo: http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html
Espero que pueda ayudar un poco. Para mayor información por favor visite:
fuente
Platform
yServices
). ¿La solución es crear un nuevo contenedor para cada combinación de clases de biblioteca inyectables?Creo que DI y posiblemente AOP generalmente no se consideran Pythonic debido a las preferencias típicas de los desarrolladores de Python, sino a las características del lenguaje.
De hecho, puede implementar un marco DI básico en <100 líneas , utilizando metaclases y decoradores de clases.
Para una solución menos invasiva, estas construcciones se pueden utilizar para incorporar implementaciones personalizadas en un marco genérico.
fuente
También está Pinject, un inyector de dependencia de Python de código abierto de Google.
Aquí hay un ejemplo
>>> class OuterClass(object): ... def __init__(self, inner_class): ... self.inner_class = inner_class ... >>> class InnerClass(object): ... def __init__(self): ... self.forty_two = 42 ... >>> obj_graph = pinject.new_object_graph() >>> outer_class = obj_graph.provide(OuterClass) >>> print outer_class.inner_class.forty_two 42
Y aquí está el código fuente
fuente
La inyección de dependencia es una técnica simple que Python admite directamente. No se requieren bibliotecas adicionales. El uso de sugerencias de tipo puede mejorar la claridad y la legibilidad.
Código marco:
class UserStore(): """ The base class for accessing a user's information. The client must extend this class and implement its methods. """ def get_name(self, token): raise NotImplementedError class WebFramework(): def __init__(self, user_store: UserStore): self.user_store = user_store def greet_user(self, token): user_name = self.user_store.get_name(token) print(f'Good day to you, {user_name}!')
Codigo del cliente:
class AlwaysMaryUser(UserStore): def get_name(self, token): return 'Mary' class SQLUserStore(UserStore): def __init__(self, db_params): self.db_params = db_params def get_name(self, token): # TODO: Implement the database lookup raise NotImplementedError client = WebFramework(AlwaysMaryUser()) client.greet_user('user_token')
Las
UserStore
sugerencias de clase y tipo no son necesarias para implementar la inyección de dependencia. Su propósito principal es brindar orientación al desarrollador del cliente. Si elimina laUserStore
clase y todas las referencias a ella, el código aún funciona.fuente
Una forma muy fácil y Pythonic de hacer la inyección de dependencias es importlib.
Podrías definir una pequeña función de utilidad
def inject_method_from_module(modulename, methodname): """ injects dynamically a method in a module """ mod = importlib.import_module(modulename) return getattr(mod, methodname, None)
Y luego puedes usarlo:
myfunction = inject_method_from_module("mypackage.mymodule", "myfunction") myfunction("a")
En mypackage / mymodule.py define myfunction
def myfunction(s): print("myfunction in mypackage.mymodule called with parameter:", s)
Por supuesto, también podría utilizar una clase iso MyClass. la función myfunction. Si define los valores del nombre del método en un archivo settings.py, puede cargar diferentes versiones del nombre del método según el valor del archivo de configuración. Django está usando un esquema de este tipo para definir su conexión a la base de datos.
fuente
Debido a la implementación de Python OOP, IoC y la inyección de dependencias no son prácticas estándar en el mundo de Python. Pero el enfoque parece prometedor incluso para Python.
Entonces mi solución es:
# Framework internal def MetaIoC(name, bases, namespace): cls = type("IoC{}".format(name), tuple(), namespace) return type(name, bases + (cls,), {}) # Entities level class Entity: def _lower_level_meth(self): raise NotImplementedError @property def entity_prop(self): return super(Entity, self)._lower_level_meth() # Adapters level class ImplementedEntity(Entity, metaclass=MetaIoC): __private = 'private attribute value' def __init__(self, pub_attr): self.pub_attr = pub_attr def _lower_level_meth(self): print('{}\n{}'.format(self.pub_attr, self.__private)) # Infrastructure level if __name__ == '__main__': ENTITY = ImplementedEntity('public attribute value') ENTITY.entity_prop
EDITAR:
Tenga cuidado con el patrón. Lo usé en un proyecto real y se mostró de una manera no tan buena. Mi publicación en Medium sobre mi experiencia con el patrón.
fuente
Después de jugar con algunos de los marcos de DI en Python, descubrí que se han sentido un poco torpes de usar al comparar lo simple que es en otros ámbitos, como con .NET Core. Esto se debe principalmente a la unión a través de elementos como decoradores que desordenan el código y dificultan simplemente agregarlo o eliminarlo de un proyecto, o unirse en función de nombres de variables.
Recientemente he estado trabajando en un marco de inyección de dependencia que en su lugar usa anotaciones de escritura para hacer la inyección llamada Simple-Injection. A continuación se muestra un ejemplo simple
from simple_injection import ServiceCollection class Dependency: def hello(self): print("Hello from Dependency!") class Service: def __init__(self, dependency: Dependency): self._dependency = dependency def hello(self): self._dependency.hello() collection = ServiceCollection() collection.add_transient(Dependency) collection.add_transient(Service) collection.resolve(Service).hello() # Outputs: Hello from Dependency!
Esta biblioteca admite la vida útil de los servicios y los servicios vinculados a las implementaciones.
Uno de los objetivos de esta biblioteca es que también es fácil agregarla a una aplicación existente y ver cómo le gusta antes de comprometerse con ella, ya que todo lo que requiere es que su aplicación tenga los tipos adecuados, y luego construya el gráfico de dependencia en el punto de entrada y ejecútelo.
Espero que esto ayude. Para obtener más información, consulte
fuente