web-dev-qa-db-fra.com

Qu'est-ce qu'une méthode Pythonic pour l'injection de dépendance?

Introduction

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?

Cas d'utilisation

Dites que ceci est le code cadre:

class FrameworkClass():
    def __init__(self, ...):
        ...

    def do_the_job(self, ...):
        # some stuff
        # depending on some external function

L'approche de base

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(...)

Question

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é?

MISE À JOUR: Situation concrète

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.

50
bagrat

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.

41
Serban Teodorescu

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.

15
Piotr Mazurek

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:

5
Roman Mogylatov

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.

1
Andrea Ratto

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

Et voici le code source

0
Nasser Abdou

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.

  • Utiliser des dépendances en tant qu'arguments, même s'il s'agit d'une classe définie dans la même base de code, constitue une approche radicalement non pythonique. Python est un langage OOP avec un modèle beau et élégant OOP, il est donc déconseillé de l’ignorer.
  • Définir des classes remplies de méthodes abstraites pour imiter le type d'interface est également étrange.
  • Les solutions de contournement d'enveloppe sur enveloppe sont trop moins élégantes pour être utilisées.
  • De plus, je n'aime pas utiliser les bibliothèques lorsque tout ce dont j'ai besoin est un petit motif.

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         
0
I159