Pour Java, Dependency Injection fonctionne comme une simple POO, c’est-à-dire que vous fournissez une interface à implémenter et que votre code de framework accepte une instance d’une classe qui implémente l’interface définie.
Maintenant, pour Python, vous pouvez faire la même chose, mais je pense que cette méthode était trop onéreuse en cas de Python. Dans ce cas, comment le mettriez-vous en œuvre de la manière pythonique?
Dites que ceci est le code cadre:
class FrameworkClass():
def __init__(self, ...):
...
def do_the_job(self, ...):
# some stuff
# depending on some external function
La manière la plus naïve (et peut-être la meilleure?) Consiste à exiger que la fonction externe soit fournie au constructeur FrameworkClass
, puis à être invoquée à partir de la méthode do_the_job
.
Code-cadre:
class FrameworkClass():
def __init__(self, func):
self.func = func
def do_the_job(self, ...):
# some stuff
self.func(...)
Code client:
def my_func():
# my implementation
framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)
La question est courte. Existe-t-il une meilleure méthode pythonique couramment utilisée pour le faire? Ou peut-être des bibliothèques supportant une telle fonctionnalité?
Imaginez que je développe un micro-framework web, qui gère l’authentification à l’aide de jetons. Ce framework a besoin d'une fonction pour fournir une ID
obtenue à partir du jeton et obtenir l'utilisateur correspondant à cette ID
.
Il est évident que le framework ne sait rien des utilisateurs ni d'aucune autre logique spécifique à l'application. Le code client doit donc injecter la fonctionnalité getter de l'utilisateur dans le framework pour que l'authentification fonctionne.
Voir Raymond Hettinger - Super considéré super! - PyCon 2015 pour un argument sur la façon d'utiliser l'héritage super et multiple au lieu de DI. Si vous n'avez pas le temps de regarder toute la vidéo, passez à la minute 15 (mais je vous conseillerais de tout regarder).
Voici un exemple d'application de ce qui est décrit dans cette vidéo à votre exemple:
Code-cadre:
class TokenInterface():
def getUserFromToken(self, token):
raise NotImplementedError
class FrameworkClass(TokenInterface):
def do_the_job(self, ...):
# some stuff
self.user = super().getUserFromToken(...)
Code client:
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(...)
Cela fonctionnera car le MRO Python garantira que la méthode client getUserFromToken est appelée (si super () est utilisé). Le code devra changer si vous êtes sur Python 2.x.
Un avantage supplémentaire ici est que cela déclenchera une exception si le client ne fournit pas d'implémentation.
Bien sûr, il ne s’agit pas vraiment d’une injection de dépendance, c’est un héritage multiple et des mixins, mais c’est une façon pythonique de résoudre votre problème.
La manière dont nous faisons l'injection de dépendance dans notre projet consiste à utiliser le fichier inject lib. Découvrez la documentation . Je recommande fortement de l'utiliser pour DI. Cela n'a aucun sens avec une seule fonction, mais commence à avoir beaucoup de sens lorsque vous devez gérer plusieurs sources de données, etc., etc.
Suivant votre exemple, cela pourrait ressembler à:
# framework.py
class FrameworkClass():
def __init__(self, func):
self.func = func
def do_the_job(self):
# some stuff
self.func()
Votre fonction personnalisée:
# my_stuff.py
def my_func():
print('aww yiss')
Quelque part dans l'application, vous voulez créer un fichier d'amorçage qui garde une trace de toutes les dépendances définies:
# bootstrap.py
import inject
from .my_stuff import my_func
def configure_injection(binder):
binder.bind(FrameworkClass, FrameworkClass(my_func))
inject.configure(configure_injection)
Et vous pourriez alors utiliser le code de cette façon:
# 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()
Je crains que ce soit aussi pythonique que possible (le module a une certaine douceur python, comme les décorateurs à injecter par paramètre, etc. - vérifiez la documentation), car python n’a pas d’éléments sophistiqués comme des interfaces ou des indicateurs de type.
Donc, pour répondre à votre question directement, ce serait très difficile. Je pense que la vraie question est: est-ce que python a un support natif pour DI? Et la réponse est malheureusement: non.
Il ya quelque temps, j’ai écrit sur microframework pour l’injection de dépendance avec l’ambition de le rendre Pythonic - Dependency Injector . Voici comment votre code peut ressembler en cas d'utilisation:
"""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)
Voici un lien vers une description plus détaillée de cet exemple - http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html
J'espère que cela peut aider un peu. Pour plus d'informations, s'il vous plaît visitez:
Je pense que DI et éventuellement AOP ne sont généralement pas considérés comme Pythonic en raison des préférences typiques des développeurs Python, plutôt que par les fonctionnalités du langage.
En fait, vous pouvez implémenter un framework DI de base en <100 lignes , en utilisant des métaclasses et des décorateurs de classe.
Pour une solution moins invasive, ces constructions peuvent être utilisées pour intégrer des implémentations personnalisées dans un cadre générique.
Il existe également Pinject, un injecteur open source python _ de Google.
Voici un exemple
>>> 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
En raison de la mise en œuvre de Python OOP, l'IoC et l'injection de dépendance ne sont pas des pratiques courantes dans le monde Python. Néanmoins, l'approche semblait prometteuse même pour Python.
Donc ma solution est:
# 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 __== '__main__':
ENTITY = ImplementedEntity('public attribute value')
ENTITY.entity_prop