Pythonの依存性注入
Lucien Chemaly
2023年10月31日
0 分で読めます依存性注入(DI)は複雑に聞こえるかもしれませんが、コードの柔軟性を高めるためのものです。ソフトウェアの適応性と堅牢性を高める、いわばブーストのようなものです。人気のプログラミング言語であるPythonでは、依存性注入を理解することが特に役立ちます。優れたソフトウェアをさらに良くする、つまり、調整や拡張が簡単で、安全性も高いソフトウェアを作ることを考えてみてください。これがPythonにおけるDIの本質です。
依存性注入を開発に取り入れる方法を知りたいなら、ここがぴったりです。このチュートリアルでは、DIの仕組みや使い方、Pythonプロジェクトをより良くする方法を解説します。
依存性注入の仕組み
DIの仕組みを理解するために、車の組み立てを考えてみましょう。すべての車がそれぞれ自分でタイヤを作らなければならないとしたら、あまり合理的ではありませんよね。自動車工場では、タイヤは別に製造され、その後車に取り付けられます。こうすれば、別のタイヤが必要になったときに交換できます。DIはこの考え方をソフトウェアの世界で活用します。
Pythonアプリケーションの内部を見てみましょう。そこにはDatabaseClientというクラスがあり、データベースに接続するための特定の情報が必要です。こうした情報をクラス内に直接記述すると、タイヤを交換できない車のようになってしまいます。しかしDIでは、情報を固定するのではなく、外部から渡します(注入します)。そのため、後になって情報を変更したり、別のデータベースを試したりしたい場合でも、アプリケーション全体を作り直す必要はありません。
このアプローチには多くのメリットがあります。用途に応じて簡単にツールを入れ替えられる工具箱のようなものです。ソフトウェアの柔軟性が高まり、実験やテストを簡単に行えるほか、更新や修正もしやすくなります。
次に、依存性注入が必要な理由を理解するため、そのメリットをいくつか見ていきましょう。
依存性注入が必要な理由
DIは、より多くの制御性をもたらし、コーディングを楽にするためのものです。プロジェクトにDIを取り入れるべき主な理由を見ていきましょう。
コードの保守性とテスト容易性を向上
依存性注入は、コンポーネント間の結合を弱め、相互依存を抑える設計パターンです。これにより、特定のコンポーネントやサービスを更新・変更する際、コードベース全体に影響を広げずに変更できます。
また、DIはテストにも適しています。コンポーネント間の結合が弱いと、ユニットテスト中にモックの依存関係を注入するのがはるかに簡単になります。個々の部分の動作を分離してテストできるため、より堅牢で包括的なテストにつながります。
依存関係を簡単に切り替えられる
データベースソリューションの移行など、技術を切り替える大変な作業に直面したことはありませんか?DIを使えば、こうした移行の負担を大幅に軽減できます。データベースとやり取りするクラスやモジュール全体を作り直す代わりに、注入する依存関係を変更するだけで済みます。この柔軟性により、移行や更新にかかる時間を大幅に短縮し、エラーの可能性も減らせます。
コードのモジュール性と再利用性を高める
依存性注入を使うと、特定の実装に強く依存しないコンポーネントを設計できます。これにより、コードベースのモジュール性が高まります。DIを前提に開発されたモジュールやコンポーネントは、アプリケーション内のさまざまな場所や、まったく別のプロジェクトでも簡単に再利用できます。開発期間の短縮や、一貫性のあるコーディング手法の促進にもつながります。
制御の反転をサポート
従来のプログラミングでは、クラスが自ら依存関係のインスタンスを生成し、管理することがよくあります。DIでは、この制御のあり方が逆転します。クラス自身が責任を負うのではなく、外部の仕組み(多くの場合、DIコンテナやフレームワーク)が依存関係のインスタンス化と提供を担います。この制御の反転(IoC)によって、依存関係の管理が簡素化され、よりすっきりと整理されたアーキテクチャを実現できます。
アプリケーションの動作を柔軟に設定
DIの目立ちにくいながらも強力なメリットの一つは、アプリケーションの動作をその場で変更できることです。依存関係の実装を切り替えることで、大量のコードを書き直さずに、アプリケーションの特定の部分の動作を変更できます。実行環境の違いに対応する場合も、さまざまなユーザーの好みに合わせる場合も、DIならアプリケーションの動作を柔軟に調整できます。
Pythonにおける依存性注入の制約
DIには多くのメリットがありますが、課題や制約もあることを認識しておくことが重要です。PythonプロジェクトにDIを取り入れるかどうかを判断する際は、次の点を考慮しましょう。
複雑さ: DIは、アプリケーション内でコンポーネントやオブジェクトをインスタンス化し、管理する方法を根本的に変えます。大規模で広範なアプリケーションでは、DIを設定するために、依存関係やインターフェース、それらを実装するクラスを細かく対応付ける必要があります。この追加レイヤーによって、定型コードや設定が増え、学習のハードルも上がる可能性があります。特に既存のプロジェクトにDIを導入する場合は顕著です。適切に導入するには、アプリケーションのアーキテクチャを深く理解する必要があります。
混乱を招く可能性: DIは抽象的な概念です。自分やチームメンバーがDIに慣れていない場合、コードベースの一部が分かりにくく感じることがあります。オブジェクトは直接インスタンス化されるのではなく、提供または注入されるため、最初はアプリケーションの処理の流れを追うのが難しくなることがあります。その結果、新しい開発者のオンボーディングに時間がかかったり、学習のハードルが高くなったりする可能性があります。
実行時エラー: DIフレームワークは、実行時に依存関係をつなぐことがよくあります。不整合や設定漏れがあると、アプリケーションがクラッシュしたり、予期しない動作をしたりする可能性があります。こうしたエラーはコンパイル時や初期テストでは見つからないことがあり、原因の特定や修正が難しくなります。適切なテストとDIへの理解によってリスクを抑えられますが、注意すべき落とし穴です。
パフォーマンスへの影響: 多くのアプリケーションではオーバーヘッドは無視できる程度ですが、実行時に依存関係を解決して注入する処理には、パフォーマンス上のコストがかかる場合があります。これは、高頻度取引プラットフォームのように、ミリ秒単位の処理速度が重要な場面で特に考慮が必要です。オーバーヘッドを把握し、必要に応じて最適化するため、アプリケーションのベンチマークとプロファイリングを行いましょう。
ロールバックの難しさ: DIでは依存関係が密接に結び付いているため、問題につながる変更を加えた場合、元に戻すのが簡単とは限りません。依存関係が連鎖的な影響を及ぼすことがあり、ある箇所の変更が複数のコンポーネントに影響する可能性があります。そのため、入念なテスト、バージョン管理、丁寧なドキュメント作成がいっそう重要になります。
必ずしも必要ではない: DIは、コンポーネント間の疎結合や個別のテスト、より優れたモジュール性が求められる複雑なアプリケーションで効果を発揮します。一方、単純なスクリプトやコンポーネントが少ない小規模なアプリケーションでは、DIの導入は過剰かもしれません。具体的なメリットがないまま、複雑さだけが増える可能性があります。他の設計パターンやアーキテクチャ上の選択と同様に、目の前のタスクに適しているかどうかを見極めることが大切です。
人気のPythonフレームワークにおける依存性注入
このセクションでは、人気のPythonフレームワークであるFlask、Django、FastAPIを使って、依存性注入を設定する方法を紹介します。各フレームワークの手法は異なりますが、依存関係の結合を弱め、保守性とモジュール性に優れ、テストしやすいアプリケーションを構築するという基本原則は共通しています。
各フレームワークの解説に進む前に、Pythonがインストールされていることを確認してください。まだインストールしていない場合は、Python公式サイトからダウンロードしてインストールできます。
Pythonをマシンに用意できたら、さまざまなフレームワークでの依存性注入を見ていきましょう。
Flaskにおける依存性注入
Flaskは軽量なWebフレームワークです。DIのサポートは組み込まれていませんが、Flask-Injectorなどの拡張機能を統合して利用できます。
FlaskとFlask-Injectorをインストールするには、次のコマンドを実行します。
Flask-InjectorはInjectorライブラリをシームレスに統合し、Flaskアプリで依存性注入を制御できるようにします。
Flaskアプリケーションを作成する
簡単なFlaskアプリケーションを作成するには、flask_di_projectという名前のフォルダを作ります。次に、そのフォルダ内にapp.pyという新しいファイルを作成し、次のコードを追加します。
このコードでは、依存性注入を使ってコンポーネントを管理するFlask Webアプリケーションを構築しています。アプリケーションはDatabase、Logger、DataServiceの3つのクラスで構成されています。コンストラクターインジェクションを使ってDataServiceクラスの依存関係を定義し、DatabaseとLoggerのインスタンスをDataServiceのインスタンス作成時に注入できるようにしています。インデックスルート関数に@injectデコレーターを付けることで、Flask-InjectorがDataServiceのインスタンスを関数に自動で注入します。さらに、configure関数で依存関係のバインドを定義するため、インスタンスが必要になったときにFlask-Injectorがその提供方法を把握できます。
アプリケーションを実行してテストする
ターミナルまたはシェルでapp.pyがあるディレクトリに移動し、次のコマンドを実行します。
ブラウザーを開き、http://127.0.0.1:5000/にアクセスします。次のメッセージが表示されます。{"message": "Data from the Database!"}

Djangoにおける依存性注入
高水準のWebフレームワークであるDjangoでは、外部ライブラリを通じてDIを利用できます。ここではDjango Injectorライブラリを使って、DjangoプロジェクトにDIを導入します。
DjangoとDjango Injectorをインストールするには、次のコマンドを実行します。
新しいDjangoプロジェクトとアプリを作成する
新しいDjangoプロジェクトとアプリケーションを作成するには、次のコマンドを実行します。
django-injectorをインストールしたら、プロジェクトに追加します。django_di_projectフォルダ内にあるsettings.pyファイルを開き、django_injectorをINSTALLED_APPS配列に追加します。
次に、myapp/views.pyでdjango-injectorを使い、DIを実現します。
ここでは、DjangoビューのMyViewを定義しています。GETリクエストでアクセスされると、依存性注入されたDataServiceを使ってデータを取得し、JSONレスポンスとして返します。
URLを更新する
django_di_project/urls.pyを更新して、ルートURLがこのビューにルーティングされるようにします。
次に、myappディレクトリにservices.pyファイルを作成し、そこでDataServiceクラスを定義します。
django-injectorを導入することで、DjangoアプリケーションにDIをシームレスに統合できました。@injectデコレーターがMyViewクラスのコンストラクターにDataServiceのインスタンスを効率的に注入し、Djangoアプリケーションのモジュール性と管理性を高めます。
アプリケーションを実行してテストする
ターミナルまたはシェルでdjango_di_projectディレクトリに移動し、次のコマンドを実行します。
ブラウザーを開き、http://127.0.0.1:8000/にアクセスします。次のメッセージが表示されます。{"message": "Hello from Dynamic DataService in Django!"}

FastAPIにおける依存性注入
FastAPIは、DIを標準でサポートする最新のWebフレームワークです。FastAPIを使えば、外部ライブラリに頼らず、DIを本来の機能として活用して、モジュール性とテスト容易性に優れたアプリケーションをシームレスに構築できます。
FastAPIとUvicorn(FastAPIを実行するためのASGIサーバー)をインストールするには、次のコードを使用します。
簡単なFastAPIアプリケーションを作成する
シンプルなFastAPIアプリケーションを作成するには、ルートディレクトリにfast_di_projectという名前の新しいフォルダを作成します。このフォルダ内にmain.pyというファイルを作成し、次のコードを追加します。
まずFastAPIクラスのインスタンスを作成します。次に、データを取得するメソッドを持つDataServiceクラスを定義します。アプリケーションのルートURLにアクセスすると、FastAPIに組み込まれた依存関係管理機能が動作します。get_data_service() 関数を作成し、DataServiceクラスのインスタンスを返すようにしています。FastAPIのDepends機能を使うと、このインスタンスをindexルート関数に注入できます。ブラウザでルートURLにアクセスすると、取得したデータと、FastAPIの世界における動的なDataServiceからのパーソナライズされたメッセージが返されます。
アプリケーションを実行してテストする
ターミナルまたはシェルでmain.pyがあるディレクトリに移動し、次のコマンドを実行します。
ブラウザを開き、http://127.0.0.1:8000/にアクセスします。次のメッセージが表示されます。{"result":{"message":"Greetings from the Dynamic DataService in the World of FastAPI!"},"info":"Fetched from the Dynamic DataService in FastAPI!"}

このチュートリアルで使用したコードは、こちらのGitHubリポジトリで公開されています。
Pythonの依存性注入フレームワーク
DIフレームワークを選ぶ際は、プロジェクト固有の要件、求めるモジュール性とテスト容易性のレベル、使用しているフレームワークを考慮することが重要です。それぞれの選択肢で利用できる機能は異なるため、プロジェクトの複雑さや設計目標に合ったものを選びましょう。代表的な選択肢をいくつかご紹介します。
Flask-Injector
Flaskアプリケーションを構築するなら、Flask-Injectorは優れた選択肢です。特に、Flaskフレームワークをベースにした軽量なプロジェクトで役立ちます。
Flask-InjectorはFlaskのエコシステムにシームレスに統合されるため、Flaskアプリで依存関係を管理し、モジュール性を確保しやすくなります。ただし、より高度な依存関係管理機能が必要となる大規模で複雑なアプリケーションでは、そうしたニーズに応じた機能を備える他のDIフレームワークも検討するとよいでしょう。
Django Injector
Djangoで開発している場合、Django Injectorライブラリが役立ちます。DjangoプロジェクトでのDIを簡素化できるため、モジュール性とテスト容易性を高めたい場合に最適です。
DIをシームレスに統合する必要があるDjangoアプリケーションを構築する際、特にクラスベースビュー、ミドルウェア、管理コマンドでDjango Injectorの使用を検討してください。
Depends
FastAPIのDepends関数は、Pythonのasyncioフレームワークと連携するように設計されており、非同期コード向けのDI機能を提供します。FastAPIを使って非同期アプリケーションを構築する場合、Dependsを利用して非同期関数やコルーチンの依存関係を管理し、注入できます。この機能は、コードの品質とモジュール設計を維持しながら、FastAPIで非同期ベースのプロジェクトに取り組む際に特に役立ちます。
Dependency Injector
Dependency Injectorライブラリは、さまざまなPythonアプリケーションに適した包括的なDIフレームワークです。コンテナ管理、スコープ設定、ライフサイクル制御などの高度な機能を備えています。
堅牢な依存関係管理や制御の反転が求められる大規模プロジェクトや、依存関係の注入と管理方法を細かく制御する必要がある複雑なシナリオでは、Dependency Injectorを使用するとよいでしょう。
Injector
Injectorライブラリは、特定のフレームワークに依存しない汎用DIフレームワークで、さまざまなPythonプロジェクトに統合できます。フレームワーク固有の統合機能を使わず、依存性注入を手動で設定・管理したいプロジェクトに特に適しています。
複数のフレームワークにまたがるアプリケーションや単独のスクリプトで、柔軟で汎用性の高いDIソリューションが必要な場合は、Injectorを使用するとよいでしょう。
Snykでプロジェクトの依存関係を安全に保つ
機能の開発に注力していても、セキュリティを後回しにはできません。そんなときに役立つのがSnykです。Snykのツールキットを使うと、次のことができます。
Pythonの依存関係をスキャンして脆弱性を特定し、修正方法の提案を受け取る。
コーディング中に、セキュリティ上の懸念についてリアルタイムのフィードバックを受け取る。
検出された脆弱性に対する自動修正を活用する。
一般的な継続的インテグレーション/継続的デリバリー(CI/CD)パイプラインにシームレスに統合し、一貫したセキュリティチェックを実施する。
ワークフローにSnyk IDE拡張機能を取り入れることで、開発の早い段階からセキュアコーディングを実践でき、依存関係の管理も簡素化できます。Snykについて詳しくは、Snyk公式ドキュメントをご覧ください。
まとめ
この記事では、Pythonで依存性注入が必要な理由を解説しました。コードの保守性やモジュール性の向上などのメリットに加え、複雑さが増したり、実行時エラーが発生したりする可能性といった課題についても紹介しました。また、Flask、FastAPI、Djangoなど、広く使われているPythonフレームワークに依存性注入を組み込む方法についても学びました。
DIにはいくつかの課題がありますが、その仕組みを理解することで、Pythonアプリケーションの品質を高められます。依存関係管理に取り組む際は、開発で常にセキュリティを重視するためにSnykなどのツールを活用することが重要です。Pythonの依存関係管理とベストプラクティスについてさらに知りたい方は、SnykブログのPython Poetryパッケージマネージャーに関する記事をご覧ください。Pythonプロジェクトをより効率的に管理するのに役立つツールを幅広く紹介しています。
CTFを始めよう
オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。
