stevedore是用来实现动态加载代码的开源模块。它是在OpenStack中用来加载插件的公共模块。可以独立于OpenStack而安装使用:https://pypi.python.org/pypi/stevedore/

stevedore使用setuptools的entry points来定义并加载插件。entry point引用的是定义在模块中的对象,比如类、函数、实例等,只要在import模块时能够被创建的对象都可以。

一:插件的名字和命名空间

一般来讲,entry point的名字是公开的,用户可见的,经常出现在配置文件中。而命名空间,也就是entry point组名却是一种实现细节,一般是面向开发者而非最终用户的。可以用Python的包名作为entry point命名空间,以保证唯一性,但这不是必须的。

entry points的主要特征就是,它可以是独立注册的,也就是说插件的开发和安装可以完全独立于使用它的应用,只要开发者和使用者在命名空间和API上达成一致即可。

命名空间被用来搜索entry points。entry points的名字在给定的发布包中必须是唯一的,但在一个命名空间中可以不唯一。也就是说,同一个发布包内不允许出现同名的entry point,但是如果是两个独立的发布包,却可以使用完全相同的entrypoint组名和entry point名来注册插件。

二:插件的使用方式

stevedore中,有三种使用插件的方式:Drivers、Hooks、Extensions

1:Drivers

一个名字对应一个entry point。使用时根据插件的命名空间和名字,定位到单独的插件:

stevedore 教程 简介-Python 技术分享 Java技术分享 Python 爬虫技术_微信公众号:zeropython—昊天博客

2:Hooks,一个名字对应多个entry point。允许同一个命名空间中的插件具有相同的名字,根据给定的命名空间和名字,加载该名字对应的多个插件。

stevedore 教程 简介-Python 技术分享 Java技术分享 Python 爬虫技术_微信公众号:zeropython—昊天博客

3:Extensions,多个名字,多个entry point。给定命名空间,加载该命名空间中所有的插件,当然也允许同一个命名空间中的插件具有相同的名字。
stevedore 教程 简介-Python 技术分享 Java技术分享 Python 爬虫技术_微信公众号:zeropython—昊天博客

三:定义并注册插件

在经过了大量的试验和总结教训之后,发现定义API最简单的方式是遵循下面的步骤:

a:使用abc模块,创建一个抽象基类来定义插件API的行为;虽然开发者无需继承一个基类,但是这种方式自有它的好处;

b:通过继承基类并实现必要的方法来创建插件

c:为每个API定义一个命名空间。可以将应用或者库的名字,以及API的名字结合起来,这种方式通俗易懂,如 “cliff.formatters”或“ceilometer.pollsters.compute”。

本节例子中创建的插件,用来对数据进行格式化输出,每个格式化方法接受一个字典作为输入,然后按照一定的规则产生要输出的字符串。格式化类可以有一个最大输出宽度的参数。

1:首先定义一个基类,其中的API需要由插件来实现

2:定义插件1

开始定义具体的插件类,这些类需要实现format方法。下面是一个简单的插件,它产生的输出都在一行上。

3:注册插件1

本例中,使用” stevedoretest.formatter”作entry points的命名空间,也就是entry points组名,源码树如下:

该发布包的setup.py内容如下:

每个entry point都以” name = module:importable ”的形式进行注册,name就是插件的名字,module就是python模块,importable就是模块中可引用的对象。

这里注册了两个插件,simple和plain,这两个插件所引用的Python对象是一样的,都是example.simple:Simple类,因此plain只是simple的别名而已。

定义好setup.py之后,运行python setup.py install即可安装该发布包。安装成功后,在该发布的egg目录中存在文件entry\_points.txt,其内容如下:

运行时,pkg\_resources在所有已安装包的entry_points.txt中寻找插件,因此不要手动编辑该文件。

4:定义插件2

使用entry points创建插件的好处之一就是,可以为一个应用独立的开发不同的插件。因此可以在另外一个发布包中定义第二个插件:

5:注册插件2

插件2的源码树如下:

在setup.py中,同样要使用”stevedoretest.formatter”作为entry points组名,该发布包的setup.py内容如下:

这里注册了插件fields,它引用的是example2.fields:FieldList类。定义好setup.py之后,运行python setup.py install即可安装该发布包。在该发布的entry_points.txt文件内容如下:

四:加载插件

1:Drivers加载

最常见的使用插件的方式是作为单独的驱动来使用,这种场景中,可以有多种插件,但只需要加载和调用其中的一个,这种情况下,可以使用stevedore的DriverManager 类。下面就是一个使用该类的例子:

其中的parser主要用来解析命令行参数的,该脚本接受三个参数,一个是format,也就是要使用的插件名字,这里默认是simple;另一个参数是--width,是插件方法可能会用到的参数,这里默认是60,该脚本还可以通过--help参数打印帮助信息:

在该脚本中,driver.DriverManager以插件的命名空间以及插件名来寻找插件,也就是entry points组名和entry points本身的名字。也就是希望通过组名和entry point本身的名字来唯一定位一个插件,但是因为相同的entry points组中可以有同名的entry point,所以,对于DriverManager来说,如果通过entry points组名和entry points本身的名字找到了多个注册的插件,则会报错。比如本例中,如果在”stevedoretest.formatter”中,有多个发布模块注册了名为”simiple”的entry point,则执行该脚本时就会报错:

因invoke_on_load为True,所以在加载该插件的时候就会调用它,这里插件引用的是一个类,所以加载插件的时候,就会实例化该类。invoke_args会以位置参数的形式,传递给该类的初始化方法,也就是用来设置输出的宽度。

当创建了一个manager之后,就已经创建好了某个具体插件类的实例。该实例的引用被关联到了manager的driver属性上,因此可以通过driver调用该实例的方法了:

运行该脚本,传入不同的参数,可以得到不同的输出格式:

2:Extensions加载

另一种使用插件的方式是一次性的加载多个扩展,可以有多个manager类支持这种使用模式,包括ExtensionManager,NamedExtensionManager和 EnabledExtensionManager。比如下面的代码:

ExtensionManager和DriverManager略有不同,它不需要提前知道要加载哪个插件,它会加载所有找到的插件。

要想调用插件,需要使用map方法,需要传给map一个函数,这里就是format_data函数,针对每个扩展都会调用该函数。format_data函数有两个参数,分别是Extension实例和map的第二个参数data:

format_data的Extension参数,是stevedore中封装插件的一个类,该类的成员有:表示插件名字的name;表示由pkg_resources返回的EntryPoint实例的entry_point,表示插件本身的plugin,也就是entry_point.load()的返回值;如果invoke_on_load为True,则还有一个成员obj表示调用plugin(*args, **kwds)后返回的结果。

map函数返回一个序列,其中的每个元素就是回调函数的返回值,也就是format_data的返回值。函数format_data返回一个元组,该元组包含扩展的名字,以及调用插件的format方法后的返回值。

插件加载的顺序是未定义的,该顺序取决于找到包的顺序,以及包中元数据文件的读取顺序,如果关心插件加载的顺序的话,可以使用NamedExtensionManager.类。下面是调用该脚本的例子:

之所以要在map中使用回调函数,而不是直接调用插件,是因为这样做可以保持应用代码和插件之间的隔离性,这种隔离性有利于应用代码和插件API的设计。

如果map直接调用插件,则每个插件必须是可调用的,这样命名空间实际上就只能用于插件的一个方法上了。使用回调函数,则插件的API就无需在应用中匹配特定的用例。

3:Hook式加载

最后一种使用插件的方式,相当于Drivers加载和Extensions加载的结合。它允许在给定的entry points组名下有同名的entry point,这样,在给定entry points组名和entry point名的情况下,hook式加载会加载所有找到的插件。

比如这里的例子,在插件2中,注册一个同样名为”simple”的插件,修改其setup.py内如如下:

这里的”simple”插件也仅仅是”fields”插件的别名而已,它与”fields”引用的对象是一样的。重新安装插件2之后,定义使用Hook加载插件的脚本如下:

这里使用hook.HookManager加载插件,参数与构建DriverManager 时是一样的,都是需要给定插件的namespace和name。又因为hook.HookManager继承自NamedExtensionManager,而NamedExtensionManager又继承自ExtensionManager。所以这里使用插件的方式与上例一样。下面是调用该脚本的例子:

更多内容,参阅原文http://docs.openstack.org/developer/stevedore/

HTTPX 基础教程-新乡seo|网站优化,网站建设_微信公众号:zeropython—昊天博客