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.

46
demandé sur bagrat 2015-07-28 17:10:11

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.

35
répondu Serban Teodorescu 2015-08-09 21:38:05

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.

13
répondu Piotr Mazurek 2015-08-04 16:29:43

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:

4
répondu Roman Mogylatov 2017-01-26 13:05:28

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.

1
répondu Andrea Ratto 2015-08-10 20:07:14

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         
0
répondu I159 2018-07-25 16:42:56