依存性注入のPythonの方法は何ですか?


84

前書き

Javaの場合、依存性注入は純粋なOOPとして機能します。つまり、実装するインターフェイスを提供し、フレームワークコードで、定義されたインターフェイスを実装するクラスのインスタンスを受け入れます。

これでPythonの場合も同じように実行できますが、Pythonの場合、この方法はオーバーヘッドが大きすぎると思います。では、Pythonの方法でどのように実装しますか?

使用事例

これがフレームワークコードだとしましょう:

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

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

基本的なアプローチ

最も素朴な(そしておそらく最良の?)方法は、外部関数をFrameworkClassコンストラクターに提供してから、do_the_jobメソッドから呼び出すことを要求することです。

フレームワークコード:

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

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

クライアントコード:

def my_func():
    # my implementation

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

質問

質問は短いです。これを行うために、より一般的に使用されるPythonicの方法はありますか?それとも、そのような機能をサポートするライブラリはありますか?

更新:具体的な状況

トークンを使用して認証を処理するマイクロWebフレームワークを開発するとします。このフレームワークにはID、トークンから取得したものを提供し、それに対応するユーザーを取得する関数が必要ですID

明らかに、フレームワークはユーザーやその他のアプリケーション固有のロジックについて何も認識していないため、認証を機能させるには、クライアントコードがユーザーゲッター機能をフレームワークに挿入する必要があります。


2
なぜあなたはしないでください「実装するインターフェイスを提供し、あなたのフレームワークのコードで実装定義されたインタフェース、そのクラスのインスタンスを受け入れますか」?Pythonでは、これをEAFPスタイルで実行します(つまり、そのインターフェイスに適合し、AttributeErrorまたはTypeErrorが他の方法で発生すると想定します)が、それ以外は同じです。
jonrsharpe 2015

デコレータでabsABCMetaメタクラスを使用してそれを行うのは簡単@abstractmethodであり、手動の検証はありません。いくつかのオプションと提案を取得したいだけです。あなたが引用したものが最もきれいなものですが、私はより多くのオーバーヘッドがあると思います。
bagrat 2015

それなら、あなたがどんな質問をしようとしているのかわかりません。
jonrsharpe 2015

わかりました、言い換えればやってみます。問題は明らかです。問題は、Pythonの方法でそれを行う方法です。オプション1:引用した方法、オプション2:質問で説明した基本的なアプローチ。だから問題は、それを行うための他のPythonの方法はありますか?
bagrat 2015

回答:


66

Raymond Hettingerを参照してください-スーパーはスーパーと見なされます!-DIの代わりにスーパー継承と多重継承を使用する方法についての議論のためのPyCon2015。ビデオ全体を見る時間がない場合は、15分にジャンプしてください(ただし、すべてを見るのをお勧めします)。

このビデオで説明されている内容を例に適用する方法の例を次に示します。

フレームワークコード:

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

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

クライアントコード:

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

Python MROはgetUserFromTokenクライアントメソッドが呼び出されることを保証するため、これは機能します(super()が使用されている場合)。Python 2.xを使用している場合は、コードを変更する必要があります。

ここでの追加の利点の1つは、クライアントが実装を提供しない場合に例外が発生することです。

もちろん、これは実際には依存性注入ではなく、多重継承とミックスインですが、問題を解決するためのPythonの方法です。


10
この答えは考慮されましたsuper():)
バグラト2015

2
レイモンドはそれをCIと呼んでいましたが、私はそれが純粋なミックスインだと思いました。しかし、PythonではミックスインとCIが実質的に同じである可能性がありますか?唯一の違いは、嫌悪感のレベルです。Mixinは依存関係をクラスレベルに注入し、CIは依存関係をインスタンスに注入します。
nad2000 2016年

1
コンストラクターレベルのインジェクションは、OPが説明したように、とにかくPythonで非常に簡単に実行できると思います。しかし、このpythonicの方法は非常に興味深いように見えます。単純なコンストラクタインジェクションIMOよりも少し多くの配線が必要です。
stucash 2017年

6
非常にエレガントだと思いますが、このアプローチには2つの問題があります。1。クラスにサーバーアイテムを注入する必要がある場合はどうなりますか?2.継承は、「is a」/特殊化の意味で最もよく使用されます。DIに使用すると、その考えに反します(たとえば、サービスをプレゼンターに注入する場合)。
AljoSt 2018

18

プロジェクトで依存性注入を行う方法は、injectlibを使用することです。ドキュメントを確認してください。DIに使用することを強くお勧めします。1つの関数だけでは意味がありませんが、複数のデータソースなどを管理する必要がある場合は多くの意味があります。

あなたの例に従うと、次のようになります。

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

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

カスタム関数:

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

アプリケーションのどこかに、定義されたすべての依存関係を追跡するブートストラップファイルを作成します。

# bootstrap.py
import inject
from .my_stuff import my_func

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

inject.configure(configure_injection)

そして、次のようにコードを使用できます。

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

pythonにはインターフェースやタイプヒントのような派手なものがないので、これは可能な限りpythonicであると思います(モジュールには、パラメーターなどで挿入するデコレーターのようなpythonの甘さがあります-ドキュメントを確認してください)。

したがって、あなたの質問に直接答えることは非常に難しいでしょう。本当の質問は、PythonにはDIのネイティブサポートがありますか?そして、その答えは、悲しいことに、いいえです。


答えてくれてありがとう、かなり面白いようです。デコレータ部分をチェックアウトします。その間、さらなる回答を待ちましょう。
bagrat 2015

'inject'ライブラリへのリンクをありがとう。これは、DIで埋めたかったギャップを埋めるのにこれまでに見つけたものに最も近いものです。ボーナスとして、実際には維持されています。
アンディモーティマー

14

少し前に、Pythonicにするという野心を持って依存性注入のマイクロフレームワークを作成しました-依存性注入。これは、使用する場合のコードの外観です。

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

この例のより詳細な説明へのリンクは次のとおりです-http://python-dependency-injector.ets-labs.org/examples/services_miniapp.html

それが少し役立つことを願っています。詳細については、以下をご覧ください。


@RomanMogylatovに感謝します。実行時にこれらのコンテナをどのように構成/適応するか、たとえば構成ファイルから知りたいのですが。これらの依存関係は、指定されたコンテナ(PlatformおよびServices)にハードコードされているようです。注入可能なライブラリクラスのすべての組み合わせに対して新しいコンテナを作成するソリューションはありますか?
Bill DeRose 2018年

2
こんにちは@BillDeRose。私の答えはSOコメントになるには長すぎると考えられていましたが、githubの問題を作成し、そこに答えを投稿しました-github.com/ets-labs/python-dependency-injector/issues/197 :)お役に立てば幸いです。ありがとう、Roman
Roman Mogylatov 2018年

3

依存性注入は、Pythonが直接サポートする単純な手法です。追加のライブラリは必要ありません。タイプヒントを使用すると、明瞭さと読みやすさが向上します。

フレームワークコード:

class UserStore():
    """
    The base class for accessing a user's information.
    The client must extend this class and implement its methods.
    """
    def get_name(self, token):
        raise NotImplementedError

class WebFramework():
    def __init__(self, user_store: UserStore):
        self.user_store = user_store

    def greet_user(self, token):
        user_name = self.user_store.get_name(token)
        print(f'Good day to you, {user_name}!')

クライアントコード:

class AlwaysMaryUser(UserStore):
    def get_name(self, token):      
        return 'Mary'

class SQLUserStore(UserStore):
    def __init__(self, db_params):
        self.db_params = db_params

    def get_name(self, token):
        # TODO: Implement the database lookup
        raise NotImplementedError

client = WebFramework(AlwaysMaryUser())
client.greet_user('user_token')

UserStoreクラスとタイプヒンティングは、依存性の注入を実現するために必要とされていません。それらの主な目的は、クライアント開発者にガイダンスを提供することです。UserStoreクラスとそのクラスへのすべての参照を削除しても、コードは引き続き機能します。


2

DIとおそらくAOPは、典型的なPython開発者の好みのために、一般的にPythonicとは見なされず、言語機能ではないと思います。

実際のところ、メタクラスとクラスデコレータを使用して、基本的なDIフレームワークを100行未満で実装できます。

侵襲性の低いソリューションの場合、これらの構成を使用して、カスタム実装を汎用フレームワークにプラグインできます。


2

GoogleによるオープンソースのPython依存性注入器であるPinjectもあります。

これが例です

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

そしてここにソースコードがあります


1

依存性注入を行う非常に簡単でPythonicな方法はimportlibです。

あなたは小さな効用関数を定義することができます

def inject_method_from_module(modulename, methodname):
    """
    injects dynamically a method in a module
    """
    mod = importlib.import_module(modulename)
    return getattr(mod, methodname, None)

そして、あなたはそれを使うことができます:

myfunction = inject_method_from_module("mypackage.mymodule", "myfunction")
myfunction("a")

mypackage / mymodule.pyで、myfunctionを定義します

def myfunction(s):
    print("myfunction in mypackage.mymodule called with parameter:", s)

もちろん、クラスMyClassisoを使用することもできます。関数myfunction。settings.pyファイルでmethodnameの値を定義すると、設定ファイルの値に応じて、異なるバージョンのmethodnameをロードできます。Djangoは、そのようなスキームを使用してデータベース接続を定義しています。


1

Python OOPの実装により、IoCと依存性注入はPythonの世界では標準的な方法ではありません。しかし、このアプローチはPythonでも有望なようです。

  • 依存関係を引数として使用することは、非Pythonのアプローチです。Pythonは美しくエレガントなOOPモデルを備えたOOP言語であり、依存関係を維持するためのより簡単な方法を提供します。
  • インターフェイスタイプを模倣するためだけに抽象メソッドでいっぱいのクラスを定義することも奇妙です。
  • ラッパーオンラッパーの巨大な回避策は、コードのオーバーヘッドを生み出します。
  • また、必要なのが小さなパターンだけの場合は、ライブラリを使用するのも好きではありません。

だから私の解決策は:

# 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         

編集:

パターンに注意してください。私はそれを実際のプロジェクトで使用しましたが、それ自体はそれほど良い方法ではありませんでした。パターンに関する私の経験についてのMediumへの私の投稿。


もちろん、IOCとDIは一般的に使用されますが、一般的に使用されないのは、良くも悪くも、DIフレームワークです。
juanpa.arrivillaga

1

PythonでいくつかのDIフレームワークを試してみたところ、.NET Coreなどの他の領域での単純さを比較すると、使用するのが少し不格好であることがわかりました。これは主に、コードを乱雑にしてプロジェクトに単純に追加または削除するのを困難にするデコレータなどを介した結合、または変数名に基づいた結合が原因です。

私は最近、Simple-Injectionと呼ばれる注入を行うために入力アノテーションを使用する依存性注入フレームワークに取り組んでいます。以下は簡単な例です

from simple_injection import ServiceCollection


class Dependency:
    def hello(self):
        print("Hello from Dependency!")

class Service:
    def __init__(self, dependency: Dependency):
        self._dependency = dependency

    def hello(self):
        self._dependency.hello()

collection = ServiceCollection()
collection.add_transient(Dependency)
collection.add_transient(Service)

collection.resolve(Service).hello()
# Outputs: Hello from Dependency!

このライブラリは、サービスの有効期間と実装へのサービスのバインドをサポートします。

このライブラリの目標の1つは、既存のアプリケーションに追加して、コミットする前にどのように気に入っているかを確認することも簡単です。必要なのは、アプリケーションに適切な型を設定することだけです。次に、で依存関係グラフを作成します。エントリポイントを実行します。

お役に立てれば。詳細については、を参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.