Qu'est-ce qu'une méthode pythonique d'Injection de dépendances?
Introduction
pour Java, L'Injection de dépendances fonctionne comme de l'OOP pur, c'est-à-dire que vous fournissez une interface à implémenter et acceptez dans votre code framework 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 au-dessus en cas de Python. Alors comment l'implémenter de la manière pythonique?
Cas D'Utilisation
Dire que c'est le cadre code:
class FrameworkClass():
def __init__(self, ...):
...
def do_the_job(self, ...):
# some stuff
# depending on some external function
L'Approche De Base
Le plus naïf (et peut-être le meilleur?) le moyen est d'exiger que la fonction externe soit fournie dans le FrameworkClass
le constructeur, et ensuite être invoqué à partir du do_the_job
méthode.
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(...)
Question
la question est courte. Est-il mieux couramment utilisés Une façon pythonique de faire ça? Ou peut-être des bibliothèques supportant une telle fonctionnalité?
mise à jour: situation concrète
Imaginez que je développe un cadre micro-web, qui gère l'authentification à l'aide de tokens. Ce cadre a besoin d'une fonction pour fournir certains ID
obtenu à partir du token et obtenir l'utilisateur correspondant à cela ID
.
évidemment, le cadre ne connaît rien des utilisateurs ou de toute autre logique spécifique à l'application, donc le code client doit injecter la fonctionnalité user getter dans le framework pour faire fonctionner l'authentification.
5 réponses
Voir Raymond Hettinger-Super considered super! - PyCon 2015 pour un argument sur la façon d'utiliser le super et l'héritage multiple à la place de DI. Si vous n'avez pas le temps de regarder toute la vidéo, sautez à la minute 15 (mais je recommande de regarder tout).
Code-Cadre:
class TokenInterface():
def getUserFromToken(self, token):
raise NotImplementedError
class FrameworkClass(TokenInterface):
def do_the_job(self, ...):
# some stuff
self.user = super().getUserFromToken(...)
Client Code:
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 parce que le MRO Python garantira que la méthode client getUserFromToken est appelée (si super() est utilisée). Le code devra changer si vous êtes sur Python 2.x.
un avantage supplémentaire ici est que cela soulèvera une exception si le client ne fournit pas une implémentation.
bien sûr, ce n'est pas vraiment une injection de dépendance, c'est un héritage multiple et des mixines, mais c'est une façon pythonique de résoudre votre problème.
la façon dont nous faisons l'injection de dépendances dans notre projet est en utilisant le injecter lib. Découvrez documentation. Je recommande fortement de l'utiliser pour DI. Cela n'a aucun sens avec une seule fonction, mais commence à avoir beaucoup de sens quand vous devez gérer plusieurs sources de données, etc.
en suivant votre exemple, cela pourrait être quelque chose de similaire à:
# 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 souhaitez créer un fichier d'amorçage, qui conserve la 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 puis vous pouvez consommer 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()
j'ai bien peur que ce soit aussi pythonique qu'il peut l'être (le module a certains Python swegness comme décorateurs à injecter par paramètre etc - vérifier les docs), car python n'a pas de trucs fantaisistes comme des interfaces ou des suggestions de type.
répondez à votre question directement serait très dur. Je pense la vraie question Est: python a-t-il un support natif pour DI? Et la réponse est malheureusement non.
Il y a quelque temps, j'ai écrit des microframes d'injection de dépendances avec l'ambition de les rendre Pythoniques - Injecteur De Dépendance. Voilà à quoi ressemble votre code 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 complète de cet exemple - http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html
j'Espère que ça peut aider un peu. Pour plus d'informations, veuillez visiter:
je pense que DI et peut-être AOP ne sont pas généralement considérés comme Pythonic en raison des préférences typiques des développeurs Python, plutôt que des fonctionnalités de langage.
en fait, vous pouvez implémenter a base DI-cadre dans <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.
en raison de L'implémentation de Python OOP, le CIO et l'injection de dépendances ne sont pas des pratiques courantes dans le monde de Python. Néanmoins l'approche semblait prometteuse même pour Python.
- pour utiliser des dépendances comme arguments, même si c'est une classe définie dans la même base de code, c'est une approche dramatiquement non-pythonique. Python est un langage OOP avec un beau et élégant modèle OOP, donc l'ignorer n'est pas une bonne idée.
- définir des classes pleines de méthodes abstraites juste pour imiter le type d'interface est bizarre aussi.
- D'énormes solutions de rechange sont trop peu élégantes pour être utilisées.
- Je n'aime pas non plus utiliser les bibliothèques quand tout ce dont j'ai besoin est un petit modèle.
Donc mon 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 __name__ == '__main__':
ENTITY = ImplementedEntity('public attribute value')
ENTITY.entity_prop