Injector - 受Guice启发的Python依赖注入框架
项目描述
简介
由于Python支持关键字参数、易于模拟对象及其动态特性,因此依赖注入在Python中很容易实现。然而,一个框架来帮助这个过程可以从大型应用程序中移除大量的模板代码。这就是Injector能发挥作用的地方。它自动递归地为您提供依赖项。作为额外的好处,Injector通过使用模块鼓励代码的模块化。
如果您不确定什么是依赖注入,或者想了解更多关于它的信息,请参阅
Injector的核心价值观是
简单性 - 虽然Injector受到Guice的启发,但它并不盲目地复制其API。提供Pythonic API胜过忠实。此外,一些功能被省略,因为支持它们可能会很繁琐并引入过多的“魔法”(成员注入、方法注入)。
与连接相关,注入器尽量减少干扰。例如,虽然你可以声明一个类的构造函数期望某些可注入参数,但类的构造函数仍然是一个标准的构造函数——如果你想,你可以手动实例化这个类。
没有全局状态——你可以拥有任意多的注入器实例,每个实例有不同的配置,每个实例在不同的作用域中有不同的对象。以下代码因为这个原因无法工作
class MyClass: @inject def __init__(t: SomeType): # ... MyClass()
这仅仅是因为没有全局的注入器可以使用。你需要明确地使用注入器.get、注入器.create_object或将MyClass注入到需要它的地方。
与静态类型检查基础设施的协作——API尽可能地提供静态类型安全性,只有在没有其他选择的情况下才会打破它。例如,注入器.get方法被类型化为injector.get(SomeType)在静态上声明为返回SomeType的实例,因此使得像mypy这样的工具可以正确地检查使用它的代码的类型。
客户端代码只了解它需要了解的依赖注入程度——`inject` <https://injector.readthedocs.io/en/latest/api.html#injector.inject>`__, `Inject` <https://injector.readthedocs.io/en/latest/api.html#injector.Inject>`__ 和 `NoInject` <https://injector.readthedocs.io/en/latest/api.html#injector.NoInject>`__ 是简单的标记,它们本身并不做任何事情,你的代码可以在没有注入器协调的情况下正常运行。
如何获取注入器?
GitHub(代码仓库,问题):https://github.com/alecthomas/injector
PyPI(可安装的,稳定分布):https://pypi.ac.cn/project/injector/。你可以使用pip安装它
pip install injector
变更日志:https://injector.readthedocs.io/en/latest/changelog.html
注入器与CPython 3.8+和实现Python 3.8+的PyPy 3兼容。
快速示例
>>> from injector import Injector, inject
>>> class Inner:
... def __init__(self):
... self.forty_two = 42
...
>>> class Outer:
... @inject
... def __init__(self, inner: Inner):
... self.inner = inner
...
>>> injector = Injector()
>>> outer = injector.get(Outer)
>>> outer.inner.forty_two
42
如果你喜欢,还可以使用dataclasses
from dataclasses import dataclass
from injector import Injector, inject
class Inner:
def __init__(self):
self.forty_two = 42
@inject
@dataclass
class Outer:
inner: Inner
injector = Injector()
outer = injector.get(Outer)
print(outer.inner.forty_two) # Prints 42
完整示例
这里有一个完整示例,以展示注入器的工作方式
>>> from injector import Module, provider, Injector, inject, singleton
我们将使用内存中的SQLite数据库作为示例
>>> import sqlite3
然后,为了示例,我们将创建一个虚构的RequestHandler类,它使用SQLite连接
>>> class RequestHandler:
... @inject
... def __init__(self, db: sqlite3.Connection):
... self._db = db
...
... def get(self):
... cursor = self._db.cursor()
... cursor.execute('SELECT key, value FROM data ORDER by key')
... return cursor.fetchall()
接下来,为了示例,我们将创建一个配置类型
>>> class Configuration:
... def __init__(self, connection_string):
... self.connection_string = connection_string
接下来,我们使用模块将配置绑定到注入器
>>> def configure_for_testing(binder):
... configuration = Configuration(':memory:')
... binder.bind(Configuration, to=configuration, scope=singleton)
接下来,我们创建一个初始化数据库的模块。它依赖于上面模块提供的配置来创建新的数据库连接,然后用一些模拟数据填充它,并提供一个Connection对象
>>> class DatabaseModule(Module):
... @singleton
... @provider
... def provide_sqlite_connection(self, configuration: Configuration) -> sqlite3.Connection:
... conn = sqlite3.connect(configuration.connection_string)
... cursor = conn.cursor()
... cursor.execute('CREATE TABLE IF NOT EXISTS data (key PRIMARY KEY, value)')
... cursor.execute('INSERT OR REPLACE INTO data VALUES ("hello", "world")')
... return conn
(注意我们如何将配置与我们的数据库初始化代码解耦。)
最后,我们初始化一个注入器并使用它来实例化一个RequestHandler实例。首先它递归地构造一个sqlite3.Connection对象,然后是它所要求的配置字典,然后实例化我们的RequestHandler
>>> injector = Injector([configure_for_testing, DatabaseModule()])
>>> handler = injector.get(RequestHandler)
>>> tuple(map(str, handler.get()[0])) # py3/py2 compatibility hack
('hello', 'world')
我们还可以验证我们的Configuration和SQLite连接确实是注入器中的单例
>>> injector.get(Configuration) is injector.get(Configuration)
True
>>> injector.get(sqlite3.Connection) is injector.get(sqlite3.Connection)
True
你可能正在想:“这只是为了给我一个数据库连接就要做这么多工作”,你的想法是对的;依赖注入通常对小型项目没有太大的用处。它在大型项目中发挥自己的优势,前期努力可以以两种方式回报:
强制解耦。在我们的例子中,这体现在解耦我们的配置和数据库配置。
配置完类型后,可以在任何地方注入而无需额外努力。只需简单地 @inject,它就会出现。这里我们并没有真正展示这一点,但你可以想象添加任意数量的 RequestHandler 子类,所有这些都将自动获得一个数据库连接。
脚注
这个框架类似于 snake-guice,但旨在简化。
© 版权所有 2010-2013 年亚历克·托马斯,BSD 许可证下
项目详情
下载文件
下载适合您平台的文件。如果您不确定该选择哪个,请了解更多关于 安装包 的信息。