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中文网其他相关文章!