Twisted 插件系统¶
本指南的目的是描述编写可扩展 Twisted 应用程序的首选方法(因此,也描述了如何扩展以这种方式编写的应用程序)。这种可扩展性是通过定义一个或多个 API 和一种机制来实现的,该机制用于收集实现此 API 的代码插件以提供一些额外的功能。该系统的基础是 twisted.plugin
模块。
使用插件系统使应用程序可扩展比其他技术具有几个明显的优势
它允许第三方开发者以松散耦合的方式轻松增强您的软件:只需要保持插件 API 的稳定性。
它允许灵活地发现新插件。例如,插件可以在程序首次运行时加载和保存,或者在程序每次启动时重新发现,或者它们可以在运行时反复轮询(允许发现程序启动后安装的新插件)。
编写可扩展程序¶
利用 twisted.plugin
是一个两步过程
定义插件需要实现的接口。这是使用 zope.interface 包完成的,与为任何其他目的定义接口的方式相同。
定义接口的约定是在名为 ProjectName/projectname/iprojectname.py 的文件中进行。本文档的其余部分将遵循该约定:假设以下接口定义位于
Matsim/matsim/imatsim.py
中,这是一个假设的材料模拟包的接口定义模块。在您的程序中的一个或多个位置,调用
twisted.plugin.getPlugins()
并迭代其结果。
作为第一步的示例,请考虑以下物理建模系统的接口定义。
from zope.interface import Interface, Attribute
class IMaterial(Interface):
"""
An object with specific physical properties
"""
def yieldStress(temperature):
"""
Returns the pressure this material can support without
fracturing at the given temperature.
@type temperature: C{float}
@param temperature: Kelvins
@rtype: C{float}
@return: Pascals
"""
dielectricConstant = Attribute("""
@type dielectricConstant: C{complex}
@ivar dielectricConstant: The relative permittivity, with the
real part giving reflective surface properties and the
imaginary part giving the radio absorption coefficient.
""")
在另一个模块中,我们可能有一个对提供 IMaterial
接口的对象进行操作的函数
def displayMaterial(m):
print('A material with yield stress %s at 500 K' % (m.yieldStress(500),))
print('Also a dielectric constant of %s.' % (m.dielectricConstant,))
最后一段所需的代码是收集 IMaterial
提供者并将它们传递给 displayMaterial
函数的代码。
from twisted.plugin import getPlugins
from matsim import imatsim
def displayAllKnownMaterials():
for material in getPlugins(imatsim.IMaterial):
displayMaterial(material)
第三方开发者现在可以通过为 IMaterial
接口实现一个或多个插件来为该建模系统贡献不同的材料。
扩展现有程序¶
上面的代码演示了如何使用 Twisted 的插件系统编写可扩展程序。但是,我们如何为它编写插件呢?本质上,我们创建提供所需接口的对象,然后在特定位置使它们可用。请考虑以下示例。
from zope.interface import implementer
from twisted.plugin import IPlugin
from matsim import imatsim
@implementer(IPlugin, imatsim.IMaterial)
class SimpleMaterial(object):
def __init__(self, yieldStressFactor, dielectricConstant):
self._yieldStressFactor = yieldStressFactor
self.dielectricConstant = dielectricConstant
def yieldStress(self, temperature):
return self._yieldStressFactor * temperature
steelPlate = SimpleMaterial(2.06842719e11, 2.7 + 0.2j)
brassPlate = SimpleMaterial(1.03421359e11, 1.4 + 0.5j)
steelPlate
和 brassPlate
现在都提供了 IPlugin
和 IMaterial
。剩下的就是使该模块在适当的位置可用。为此,有两个选项。第一个主要在开发期间有用:如果添加到 sys.path
的目录(通常通过将其添加到 PYTHONPATH
环境变量中)包含一个名为 twisted/plugins/
的目录,则该目录中的每个 .py
文件都将作为插件源加载。此目录不能是 Python 包:包含 __init__.py
将导致跳过该目录,并且不会从该目录加载任何插件。其次,Twisted 的 twisted.plugins
包的已安装版本的每个模块也将作为插件源加载。
一旦以这两种方式之一安装了此插件,就可以运行 displayAllKnownMaterials
,我们将看到两对输出:一对用于钢板,一对用于黄铜板。
备用插件包¶
getPlugins
接受一个上面没有提到的额外参数。如果传入,则第二个参数应该是要代替 twisted.plugins
用作插件元包的模块或包。如果您正在为 Twisted 接口编写插件,则您永远不需要传递此参数。但是,如果您已经开发了自己的接口,您可能希望强制其插件安装在您自己的插件包中,而不是 Twisted 的插件包中。
您可能希望支持 yourproject/plugins/
目录以方便开发。为此,您应该使 yourproject/plugins/__init__.py
至少包含以下几行。
from twisted.plugin import pluginPackagePaths
__path__.extend(pluginPackagePaths(__name__))
__all__ = []
这里的关键行为是接口本质上与特定的插件包配对。如果插件安装在与依赖于其提供的接口的代码不同的包中,则应用程序加载它们时将找不到这些插件。
插件缓存¶
在使用 Twisted 插件系统时,您可能会注意到在不同位置出现 dropin.cache
文件。这些文件用于缓存有关包含它们的目录中存在哪些插件的信息。有时,此缓存信息可能会过时。Twisted 使用插件系统中涉及的各种文件的 mtime 来确定何时此缓存可能已失效。Twisted 会尝试在每次尝试使用缓存但发现它已过时时重新写入缓存。
对于站点范围的安装,以普通用户身份运行的应用程序可能无法(实际上也不应该)重写缓存文件。虽然这些应用程序仍然可以运行并找到正确的插件信息,但它们的运行速度可能比缓存更新时的速度慢,并且如果某些插件已被删除但缓存仍然引用它们,它们也可能会报告异常。出于这些原因,在安装或删除提供 Twisted 插件的软件时,站点管理员应确保重新生成缓存。此类软件的良好行为的包管理器应承担此任务,因为它是可以轻松自动化的。重新生成缓存的规范方法是运行以下 Python 代码
from twisted.plugin import IPlugin, getPlugins
list(getPlugins(IPlugin))
如前所述,如果插件已被删除,则在此处引发异常是正常的。