Python方式の依存性注入

なぜ依存性注入が必要なのですか? アプリケーション内のコンポーネントの接続性が低下し、テストが簡素化されます。 一部の開発者は、依存性注入は大規模なプロジェクトでのみ必要であり、プログラムを非常に複雑にするという意見を持っています。 これは歴史的に、SpringやJus in Javaのような人気のあるフレームワークによるものだと思います。 特に、信じられないほどの組み合わせであるSpringのために。



Python-injectは、 Pythonでの依存性注入のための小さなライブラリです。 3番目のバージョンは、UNIXスタイルで記述されています。 完璧に機能するのは1つの機能のみであり、すべてを実行しようとするわけではありません。 既に述べたSpringやJusとは異なり、Injectは開発者からクラスコンストラクターを盗み出したり、特定のスタイルでアプリケーションを記述する必要を開発者に課したり、アプリケーションオブジェクトのグラフ全体を管理しようとはしません。



インジェクションは、実際には設定を必要とせず(さらにローリングによる)、非常に使いやすいです。

たとえば、テストでは
#   class Db(object): pass class Mailer(object): pass #      class User(object): db = inject.attr(Db) mailer = inject.attr(Mailer) def __init__(self, name): self.name = name def register(self): self.db.save(self) self.mailer.send_welcome_email(self.name) #    inmemory    . class TestUser(unittest.TestCase): def setUp(self): inject.clear_and_configure(lambda binder: binder \ .bind(Db, InMemoryDb()) \ .bind(Mailer, Mock())) self.mailer = inject.instance(Mailer) def test_register__should_send_welcome_email(self): #  . user = User('John Doe') #   . user.register() #     . self.mailer.send_welcome_email.assert_called_with('John Doe')
      
      











使用する



プロジェクトのWebサイトからアーカイブをダウンロードできますが、PyPIでインストールするのが最適です。

 [sudo] pip install inject
      
      







アプリで:

 #   . import inject #    def my_config(binder): binder.install(my_config2) #    binder.bind(Db, RedisDb('localhost:1234')) binder.bind_to_provider(CurrentUser, get_current_user) #  . inject.configure(my_config) #     inject.instance  inject.attr class User(object): db = inject.attr(Db) @classmethod def load(cls, id): return cls.db.load('user', id) def __init__(self, id): self.id = id def save(self): self.db.save('user', self) def foo(bar): cache = inject.instance(Cache) cache.save('bar', bar) #      #    . user = User(10) user.save()
      
      







バインディングの種類



インジェクターの構成は、バインダーを使用して説明されています。 バインディングは初期化を担当します

依存関係。 4つのタイプがあります。

  1. 常に同じオブジェクトを返すインスタンスバインディング:

     redis = RedisCache(address='localhost:1234') def config(binder): binder.bind(Cache, redis)
          
          





  2. 最初の呼び出しでシングルトンを作成するコンストラクタバインディング:

     def config(binder): # Creates a redis cache singleton on first injection. binder.bind_to_constructor(Cache, lambda: RedisCache(address='localhost:1234'))
          
          





  3. 依存関係が注入されるたびに呼び出されるプロバイダーバインディング:

     def get_my_thread_local_cache(): # Custom code here pass def config(binder): # Executes the provider on each injection. binder.bind_to_provider(Cache, get_my_thread_local_cache)
          
          





  4. これらのクラスの構成に明示的なバインディングがない場合、クラスシングルトーンを自動的に作成するランタイムバインディング。 実行時バインディングにより、構成サイズが大幅に削減されます。 たとえば、次のコードでは、Configクラスのみに明示的な構成があります。

     class Config(object): pass class Cache(object): config = inject.attr(Config) class Db(object): config = inject.attr(Config) class User(object): cache = inject.attr(Cache) db = inject.attr(Db) @classmethod def load(cls, user_id): return cls.cache.load('users', user_id) or cls.db.load('users', user_id) inject.configure(lambda binder: binder.bind(Config, load_config_file())) user = User.load(10)
          
          









スコープ(スコープ)がないのはなぜですか?



JavaアプリケーションでSpringとJusを長年使用してきた私は、それらのスコープ(コンセプト自体)を愛していませんでした。 デフォルトでは、注入はすべてのオブジェクトをシングルトーンとして作成します。 すべてのオブジェクトをコンテキスト/インジェクター内で初期化する必要があるSpringやJusとは異なり、通常のクラスコンストラクターを使用できるため、プロトタイプスコープ/ NO_SCOPEは必要ありません。



他のスコープ、たとえば、リクエストスコープやセッションスコープは脆弱です。私の意見では、アプリケーション内のコンポーネントの接続性を高め、テストするのは困難です。 インジェクションでは、オブジェクトのスコープを制限する必要がある場合、いつでも独自のプロバイダーを作成できます。



おわりに



これは、注入の3番目のバージョンです。 最初の2つは、SpringとJuiceに部分的に類似しており、収穫者にもなろうとしました。 最新バージョンはそれらに比べて小さいですが、シンプルで柔軟で使いやすいです。 プロジェクトコードはGithubにあります。



All Articles