前言
寫了一段時間的程式之後,你有沒有發現自己的程式碼裡到處都是這樣的東西:
db = MySQLDatabase(host="localhost", port=3306)
service = UserService(db) 乍看沒問題,但當你要換資料庫、寫單元測試、或讓同一個 service 支援不同環境時,因為已經寫死了要用哪個 db,這種寫法就會很不方便。
Python Injector 就是用來解決這個問題的套件,透過依賴注入(Dependency Injection)的方式,讓你的元件不用知道「我要跟誰拿資源」,而是由外部統一管理。
讀完這篇文章,你會學到:
- 什麼是依賴注入,為什麼它讓程式碼更好維護
- 如何安裝並開始使用 `injector` 套件
- 用 Module 組合複雜的依賴關係
如果還不知道什麼是依賴注入,可以先看 Python 中的依賴注入。
為什麼選擇 Injector?
選 injector 的理由很簡單,它完全依賴 Python 的 type hints 驅動,不需要額外寫設定檔,也符合現在 Python 的環境。如果你的專案有用 type hints,那就更方便了。
適合使用 injector 的情況:
- 專案有多層服務(service → repository → database)
- 有寫單元測試的習慣,需要 mock 依賴
- 想讓不同環境(開發、測試、正式)使用不同實作
不適合的情況:
- 小腳本或單一功能的工具,手動傳參數就夠了
- 對 DI 概念不熟悉,引入反而增加溝通成本
快速開始:安裝與基本用法
安裝
pip install injectorStep 1:定義你的依賴類別
from injector import inject, Injector, Module, provider, singleton
class DatabaseConfig:
"""資料庫連線設定。"""
def __init__(self, host: str = "localhost", port: int = 5432):
self.host = host
self.port = port
class Database:
@inject
def __init__(self, config: DatabaseConfig):
self.config = config
print(f"連線到 {config.host}:{config.port}")
def query(self, sql: str) -> list[dict]:
# 這裡會執行 SQL
return [{"id": 1, "name": "Alice"}]Step 2:在 Service 層使用 Inject
@inject 是 injector 的核心裝飾器,告訴框架「這個 __init__ 的參數,請幫我自動注入」。
class UserService:
@inject
def __init__(self, db: Database):
self.db = db
def get_all_users(self) -> list[dict]:
return self.db.query("SELECT * FROM users")Step 3:用 Injector 建立實例
不需要宣告:
UserService(Database(DatabaseConfig()))交給 Injector 處理:
from injector import inject, Injector
injector = Injector()
service = injector.get(UserService)
users = service.get_all_users()
print(users)
# Output: [{'id': 1, 'name': 'Alice'}]Injector 會自動解析整個依賴關係:UserService → Database → DatabaseConfig,不用手動 new 一個新的。
進階用法:Module 與 Singleton
有時候我們不想用預設的建構子邏輯,想自己控制「這個類別怎麼被建立」,這時候就要用 Module。
injector 預設會用類別的 __init__ 來建立實例。但有時候我們不想用預設的方式,想自己控制建立方法,這時候就在 Module 裡寫一個方法,加上 @provider:
class AppModule(Module):
@provider
@singleton
def provide_database_config(self) -> DatabaseConfig:
# 這裡可以從環境變數、設定檔讀取
return DatabaseConfig(host="prod-db.example.com", port=5432)@singleton 可以讓整個服務只建立一次,有些資源例如資料庫連線通常不應該每次都重新建立,用 singleton 就可以確保整個 app 共用同一個 instance。
組合 Module 建立 Injector
injector = Injector([AppModule()])
service = injector.get(UserService)
users = service.get_all_users()
# Output: 連線到 prod-db.example.com:5432
# Output: [{'id': 1, 'name': 'Alice'}]測試時替換假實作
這是 DI 最讚的地方——測試時完全不用動 `UserService` 的程式碼:
class TestModule(Module):
@provider
def provide_database(self) -> Database:
# 回傳一個 mock 版本,不會真的連線
class FakeDatabase(Database):
def query(self, sql: str) -> list[dict]:
return [{"id": 99, "name": "Test User"}]
return FakeDatabase(DatabaseConfig())
# 測試時注入 TestModule
test_injector = Injector([TestModule()])
service = test_injector.get(UserService)
print(service.get_all_users())
# Output: [{'id': 99, 'name': 'Test User'}]總結
依賴注入不是某個框架的功能,而是一種設計習慣。
當我們把「建立依賴」和「使用依賴」這兩件事分開,程式碼的每個部分就只需要專注在自己的責任上,service 只管業務邏輯,repository 只管資料存取,這樣的架構帶來的好處不只是換資料庫變容易,更重要的是:程式碼能夠變得「可被替換」。測試時換掉真實的外部連線、不同環境用不同的實作、日後想重構某個元件,這些事情都不會再動到業務邏輯本身。



