Python 3.5 版本引入了“類型提示”,使程式碼更易讀,方便開發者理解彼此的程式碼。
在 Java、C 等強型別語言中,依賴反轉 (DI - Dependency Inversion) 是一項重要的技術,但在弱型別語言中難以實現。
依賴反轉的核心思想是:類別不應該依賴具體的實現,而應該依賴抽象。因為抽象(介面或抽象類別)是相對穩定的契約。
不良範例:
<code class="language-python">class GasStation: def fill_tank(car, amount): car.fill(amount)</code>
此例中,加油站只能為汽車加油。更糟的是,由於函數 fill_tank
沒有定義類型,任何值都可能被傳入,錯誤只能在運行時才能發現。
良好範例:
<code class="language-python">from typing import Protocol class Vehicle(Protocol): def fill(amount: int) -> None: ... class GasStation: def fill_tank(vehicle: Vehicle, amount: int) -> None: vehicle.fill(amount)</code>
此例中,先定義抽象類別 Vehicle
(使用 typing.Protocol
)。 GasStation
的 fill_tank
函數不再依賴具體的汽車類,而是依賴 Vehicle
接口,從而變得更通用,可以為任何實現了 fill
方法的車輛加油。
我利用 Python 的類型提示系統,創建了一個簡化依賴反轉使用的函式庫,名為 PyDIT (Python Dependency Injection with Types)。
假設需要一個用於儲存使用者資料的資料庫接口,無論使用 PostgreSQL、MySQL、OracleDB、記憶體資料庫或 NoSQL 資料庫,都需要實作資料庫連接類,並提供讀取、寫入、刪記錄的功能。
<code class="language-python">from time import sleep from typing import TypedDict from typing_extensions import override from uuid import UUID from src.configs.di import pydit from src.adapters.repositories.interfaces.user import UserRepository from src.constants.injection import MEMORY_REPOSITORY_CONFIG_TOKEN from src.domain.user.models.user import UserModel class ConfigType(TypedDict): delay: int class MemoryUserRepository(UserRepository): __users: dict[UUID, UserModel] = {} def __init__(self): self.__delay = self.config.get("delay", 0.2) @pydit.inject(token=MEMORY_REPOSITORY_CONFIG_TOKEN) def config(self) -> ConfigType: # TODO: supress return type error pass @override def get_by_id(self, *, id_: UUID) -> UserModel: sleep(self.__delay) user = self.__users.get(id_) if user is None: raise ValueError("User not found") return user @override def save(self, *, data: UserModel) -> None: sleep(self.__delay) self._check_pk_conflict(pk=data.id) self.__users[data.id] = data @override def list_(self) -> list[UserModel]: return list(self.__users.values()) def _check_pk_conflict(self, *, pk: UUID) -> None: if pk not in self.__users: return raise ValueError("Primary key conflicts: DB alrady has a user with this ID")</code>
為了確保程式碼與資料庫技術無關,定義一個所有資料庫類別都必須遵循的介面:
<code class="language-python">from abc import abstractmethod from typing import Protocol from uuid import UUID from src.domain.user.models.user import UserModel class UserRepository(Protocol): @abstractmethod def get_by_id(self, *, id_: UUID) -> UserModel: pass @abstractmethod def save(self, *, data: UserModel) -> None: pass @abstractmethod def list_(self) -> list[UserModel]: pass</code>
接下來,初始化相依性以便注入:
<code class="language-python">from src.adapters.repositories.in_memory.user import MemoryUserRepository from src.constants.injection import MEMORY_REPOSITORY_CONFIG_TOKEN from .di import pydit from .get_db_config import get_db_config def setup_dependencies(): pydit.add_dependency(get_db_config, token=MEMORY_REPOSITORY_CONFIG_TOKEN) pydit.add_dependency(MemoryUserRepository, "UserRepository")</code>
最後,將相依性注入到建立使用者的模組中:
<code class="language-python">from typing import cast from src.adapters.repositories.interfaces.user import UserRepository from src.configs.di import pydit from src.domain.user.models.create_user import CreateUserModel from src.domain.user.models.user import UserModel from src.domain.user.services.create import CreateUserService from src.domain.user.services.list import ListUsersService class UserModule: @pydit.inject() def user_repository(self) -> UserRepository: return cast(UserRepository, None) def create(self, data: CreateUserModel) -> None: CreateUserService(self.user_repository).execute(data) def list_(self) -> list[UserModel]: return ListUsersService().execute()</code>
依賴項作為屬性注入,可以透過 self
或 module.user_repository
存取。
這個範例很簡單,但 PyDIT 可以應用於各種專案配置、程式碼抽象化和 SOLID 原則的場景。歡迎嘗試並貢獻程式碼!
代碼倉庫:Github
LinkedIn:Marcelo Almeida (MrM4rc)
PyPI:python-pydit
以上是Python 中輸入的影響的詳細內容。更多資訊請關注PHP中文網其他相關文章!