web-dev-qa-db-ja.com

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

前書き

Javaの場合、Dependency Injectionは純粋な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(...)

質問

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

更新:具体的な状況

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

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

60
bagrat

DIの代わりにスーパーおよび多重継承を使用する方法についての議論については、 レイモンド・ヘッティンガー-スーパーをスーパーと見なしました!-PyCon 2015 を参照してください。ビデオ全体を見る時間がない場合は、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()が使用される場合)。コードがPython2.x。

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

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

46

プロジェクトで依存性注入を行う方法は、 inject libを使用することです。 ドキュメント を確認してください。 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ファイルを作成します。

# 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のネイティブサポートを持っていますか?そして、答えは、悲しいことに、いいえです。

16
Piotr Mazurek

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

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

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

7
Roman Mogylatov

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

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

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

1
Andrea Ratto

また、Pinject、オープンソースpython Googleの依存性インジェクターもあります。

ここに例があります

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

そして ソースコードはこちら

0
Nasser Abdou

Python OOP実装IoCと依存性注入は、Python world。 Python用。

  • 同じコードベースで定義されたクラスであっても、依存関係を引数として使用することは、劇的に非Python的なアプローチです。 Python is OOP美しくエレガントな言語OOPモデルなので、無視するのは得策ではありません。
  • インターフェイス型を模倣するためだけに抽象メソッドでいっぱいのクラスを定義するのも奇妙です。
  • ラッパー上での巨大な回避策は、使用するにはエレガントすぎます。
  • また、必要なのが小さなパターンだけである場合、ライブラリを使用するのは好きではありません。

だから私の solution は:

# 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