it-swarm.com.de

Was ist ein pythonischer Weg für die Abhängigkeitseinspritzung?

Einführung

Für Java arbeitet Dependency Injection als reine OOP, d. H. Sie stellen eine zu implementierende Schnittstelle bereit und akzeptieren in Ihrem Framework-Code eine Instanz einer Klasse, die die definierte Schnittstelle implementiert.

Nun, für Python können Sie den gleichen Weg machen, aber ich denke, dass diese Methode zu viel Aufwand für Python war. Wie würden Sie es dann auf die Pythonic-Art implementieren?

Anwendungsfall

Sagen Sie, das ist der Rahmencode:

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

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

Der grundlegende Ansatz

Der naivste (und vielleicht der beste?) Weg besteht darin, dass die externe Funktion in den Konstruktor FrameworkClass eingegeben und dann von der do_the_job-Methode aufgerufen wird.

Rahmencode:

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

    def do_the_job(self, ...):
        # some stuff
        self.func(...)

Kundencode:

def my_func():
    # my implementation

framework_instance = FrameworkClass(my_func)
framework_instance.do_the_job(...)

Frage

Die Frage ist kurz. Gibt es eine bessere, häufig verwendete Pythonic-Methode, um dies zu tun? Oder vielleicht Bibliotheken, die solche Funktionen unterstützen?

UPDATE: Konkrete Situation

Stellen Sie sich vor, ich entwickle ein Mikro-Web-Framework, das die Authentifizierung mit Token übernimmt. Dieses Framework benötigt eine Funktion, um einige aus dem Token abgerufene ID bereitzustellen und den Benutzer entsprechend dieser ID zu erhalten.

Offensichtlich kennt das Framework nichts über Benutzer oder andere anwendungsspezifische Logik. Daher muss der Clientcode die Benutzer-Getter-Funktionalität in das Framework einfügen, damit die Authentifizierung funktioniert.

50
bagrat

See Raymond Hettinger - Super als super! - PyCon 2015 für ein Argument zur Verwendung von Super- und Mehrfachvererbung anstelle von DI. Wenn Sie keine Zeit haben, das gesamte Video anzusehen, springen Sie zu Minute 15 (ich würde jedoch empfehlen, das ganze Video anzusehen). 

Hier ein Beispiel, wie Sie das in diesem Video beschriebene auf Ihr Beispiel anwenden können:

Framework Code:

class TokenInterface():
    def getUserFromToken(self, token):
        raise NotImplementedError

class FrameworkClass(TokenInterface):
    def do_the_job(self, ...):
        # some stuff
        self.user = super().getUserFromToken(...)

Clientcode:

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

Dies funktioniert, da der Python-MRO garantiert, dass die Clientmethode getUserFromToken aufgerufen wird (wenn super () verwendet wird). Der Code muss sich ändern, wenn Sie Python 2.x verwenden.

Ein zusätzlicher Vorteil hierbei ist, dass dies eine Ausnahme auslöst, wenn der Client keine Implementierung bereitstellt.

Natürlich ist dies nicht wirklich eine Abhängigkeitsinjektion, es ist mehrfache Vererbung und Mixins, aber es ist ein Pythonic-Weg, um Ihr Problem zu lösen.

41

Die Abhängigkeitsinjektion in unserem Projekt erfolgt mithilfe von inject lib. Schauen Sie sich die Dokumentation an . Ich empfehle die Verwendung für DI. Es macht mit einer einzigen Funktion keinen Sinn, aber es macht Sinn, wenn Sie mehrere Datenquellen usw. verwalten müssen.

Ihrem Beispiel folgend könnte es ähnlich sein:

# framework.py
class FrameworkClass():
    def __init__(self, func):
        self.func = func

    def do_the_job(self):
        # some stuff
        self.func()

Ihre benutzerdefinierte Funktion:

# my_stuff.py
def my_func():
    print('aww yiss')

Sie möchten irgendwo in der Anwendung eine Bootstrap-Datei erstellen, die alle definierten Abhängigkeiten aufzeichnet:

# bootstrap.py
import inject
from .my_stuff import my_func

def configure_injection(binder):
    binder.bind(FrameworkClass, FrameworkClass(my_func))

inject.configure(configure_injection)

Und dann könnten Sie den Code folgendermaßen verbrauchen:

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

Ich fürchte, das ist so viel Pythonic, wie es nur kann (das Modul hat etwas Python-Süße wie Dekorateure, die durch Parameter usw. eingefügt werden müssen - überprüfen Sie die Dokumentation), da Python keine ausgefallenen Dinge wie Schnittstellen oder Typhinweise hat.

Es wäre also sehr schwer, Ihre Frage beantworten direkt zu beantworten. Ich denke, die wahre Frage ist: Hat Python native Unterstützung für DI? Und die Antwort lautet leider: Nein.

15
Piotr Mazurek

Vor einiger Zeit habe ich das Abhängigkeitsinjektionsmikroframework mit dem Bestreben geschrieben, es als Pythonic - Dependency Injector zu erstellen. So kann Ihr Code bei Verwendung aussehen:

"""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)

Hier ist ein Link zu einer ausführlicheren Beschreibung dieses Beispiels - http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html

Ich hoffe es kann ein bisschen helfen. Für weitere Informationen, besuchen Sie bitte:

5
Roman Mogylatov

Ich denke, dass DI und möglicherweise AOP im Allgemeinen nicht als Pythonic betrachtet werden, da typische Python-Entwickler-Präferenzen es vorziehen, eher die Sprachfeatures.

Tatsächlich können Sie ein einfaches DI-Framework in <100 Zeilen implementieren, indem Sie Metaklassen und Klassendekorateure verwenden.

Für eine weniger invasive Lösung können diese Konstrukte verwendet werden, um benutzerdefinierte Implementierungen in ein generisches Framework einzufügen.

1
Andrea Ratto

Es gibt auch Pinject, einen Open-Source-Abhängigkeitsinjektor python von Google.

Hier ist ein Beispiel

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

Und hier ist der Quellcode

0
Nasser Abdou

Aufgrund der Implementierung von Python OOP sind IoC und Abhängigkeitsinjektion in der Python-Welt nicht üblich. Trotzdem schien der Ansatz selbst für Python vielversprechend.

  • Abhängigkeiten als Argumente zu verwenden, selbst wenn es sich um eine Klasse handelt, die in derselben Codebasis definiert ist, ist der Ansatz nicht Pythonic. Python ist OOP Sprache mit einem schönen und eleganten OOP Modell, daher ist es keine gute Idee, es zu ignorieren.
  • Es ist auch komisch, Klassen mit abstrakten Methoden zu definieren, um den Schnittstellentyp zu imitieren.
  • Umfassende Wrapper-on-Wrapper-Workarounds sind zu unauffällig, um verwendet zu werden.
  • Ich benutze auch keine Bibliotheken, wenn ich nur ein kleines Muster brauche.

Also meine Lösung ist:

# 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