Python插件之stevedore
1. 插件的基本使用
1. 简介
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名来注册插件。
2. 分类
在stevedore中,有三种使用插件的方式:Drivers、Hooks、Extensions
Drivers
一个名字对应一个entry point。使用时根据插件的命名空间和名字,定位到单独的插件:
Extensions
多个名字,多个entry point。给定命名空间,加载该命名空间中所有的插件,当然也允许同一个命名空间中的插件具有相同的名字。
Hooks
一个名字对应多个entry point。允许同一个命名空间中的插件具有相同的名字,根据给定的命名空间和名字,加载该名字对应的多个插件。
3. 结构
stevedore通过提供manager类来实现动态加载扩展插件的管理,因此在实现stevedore时,首先为其他父类定义了一个manager基类ExtensionManager类。ExtensionManager类是一个所有其他manager类的基类,其主要的属性和方法如下:
namespace:string类型,命名空间,表示entry points的命名空间。
invoke_on_load:bool类型,表示是否自动加载扩展插件。
invoke_args:tuple类型,表示自动加载extension时传入的参数。
invoke_kwds:dict类型,表示自动加载extension时传入的参数。
propagate_map_exceptions:bool类型,表示使用map调用时,是否向上传递调用信息。
on_load_failure_callback:func类型,表示加载失败时调用的方法。
verify_requirements:bool类型,表示是否使用setuptools安装插件所需要的依赖。
map(func, *args, kwds):为每一个extension触发func()函数。
map_method(method_name, *args, kwds):为每一个extension触发method_name指定的函数。
names():获取所有发现的extension名称。
entry_points_names():返回所有entry_points的名称列表,每个列表元素是一个有entry points的名称和entry points列表的map对象。
list_entry_points():某个命名空间的所有entry points列表。
stevedore中其他所有manager类都需要继承ExtensionManager类,而ExtensionManager类初始化时便会通过namespace等加载所有extension,并对插件进行初始化。
def __init__(self, namespace,
invoke_on_load=False,
invoke_args=(),
invoke_kwds={},
propagate_map_exceptions=False,
on_load_failure_callback=None,
verify_requirements=False):
self._init_attributes(
namespace,
propagate_map_exceptions=propagate_map_exceptions,
on_load_failure_callback=on_load_failure_callback)
extensions = self._load_plugins(invoke_on_load,
invoke_args,
invoke_kwds,
verify_requirements)
在ExtensionManager实例化对象时,首先调用_init_attributes()方法初始化namespace等参数,然后会调用_load_plugins()方法加载所有的extension插件;最后会调用_init_plugins()方法设置对象的属性。
在定义ExtensionManager时,还涉及到一个重要的类Extension,该类表示一个extension,该类主要包含如下属性:
- name:表示一个entry point的名称。
- entry_point:表示从pkg_resources获得的一个EntryPoint对象。
- plugin:通过调用entry_point.load()方法返回的plugin类。
- obj:extension被manager类加载时,会调用plugin(*args, **kwds)返回一个plugin对象。
在ExtensionManager的map()方法中,为每一个entry point调用func()函数,而func()函数的第一个参数即为Extension对象。
4. 加载方式
根据entry points配置的不同,stevedore提供了三种加载插件的方式:ExtensionManager、DriverManager、HookManager。下面将分别介绍这三种加载插件的方式:
ExtensionManager:一种通用的加载方式。这种方式下,对于给定的命名空间,会加载该命名空间下的所有插件,同时也允许同一个命名空间下的插件拥有相同的名称,其实现即为stevedore.extension.ExtensionManager类。
HookManager:在这种加载方式下,对于给定的命名空间,允许同一个命名空间下的插件拥有相同的名称,程序可以根据给定的命名空间和名称加载该名称对应的多个插件,其实现为stevedore.hook.HookManager类。
DriverManager:在这种加载方式下,对于给定的命名空间,一个名字只能对应一个entry point,对于同一类资源有多个不同插件的情况,只能选择一个进行注册;这样,在使用时就可以根据命名空间和名称定位到某一个插件,其实现为stevedore.driver.DriverManager类。
在实现这些加载方式的类时,stevedore还定义了多个其他类型的辅助manager类,这些manager类之间的关系如下图所示。
由图可知,ExtensionManager类时所有stevedore的manager类的父类,DriverManager类和HookManager类是ExtensionManager子类NamedExtensionManager类的子类。而NamedExtensionManager类中增加了一个属性names,所以DriverManager类和HookManager类在加载对应插件时,只加载names属性所包含的名称的entry point插件。除了这几个类之外,stevedore还定义了其他三个辅助的manager类:
- EnabledExtensionManager类:该类在ExtensionManager类的基础上添加了一个check_func属性,表示一个验证方法,因此在加载时只加载通过check_func()方法验证的extension插件。
- DispatchExtensionManager类:该类继承自EnabledExtensionManager类,该类重写了ExtensionManger类中定义的map()和map_method()方法,其为这两个方法添加了filter_func参数,表示只对通过filter_func()方法过滤的extension才会执行func()函数。
- NameDispatcherExtensionManager类:该类继承自DispathExtensionManager类,该类也定义了一个names属性,在使用时,只有names包含的名称的extension执行map()和map_method()方法时才会执行对应的func()方法。
2. 插件使用示范
先建立项目目录: mystevedore
然后新建一个目录: people,该目录
下面分别包含:
init.py
chinese.py
english.py
pluginBase.py
文件
init.py 文件内容为空
pluginBase.py文件内容如下
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class PeopleBase(object):
def __init__(self):
pass
@abc.abstractmethod
def say(self):
return "say hi"
chinese.py文件内容如下
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pluginBase import PeopleBase
class Chinese(PeopleBase):
def say(self):
return "ni hao"
english.py文件内容如下
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pluginBase import PeopleBase
class Chinese(PeopleBase):
def say(self):
return "hello"
项目目录: mystevedore项目目录下新建一个文件setup.py,文件内容如下
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import find_packages
from setuptools import setup
setup(
name='mm_stevedore_test',
version='1.0',
packages=find_packages(),
entry_points={
'people.say': [
'chinese = people.chinese:Chinese',
'english = people.english:English'
],
}
)
然后执行如下命令安装该上述编写的插件
python setup.py install
最后编写调用插件的文件 main.py,内容如下
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from stevedore import driver
'''
关键:
1 stevedore
含义:动态加载代码的库,允许在运行时通过发现和加载扩展(插件)应用。
使用setuptools的entry points来定义并加载插件。
entry point引用的是定义在模块中的对象(类,对象,实例),只要在import模块时能够被创建
2 entry points
entry points:本质上是提供给外界调用的程序或者类
entry points可以独立注册,需要开发者和使用者在命名空间和插件名称上一致
3 插件
插件种类:
1) stevedore.driver.DriverManager :一个名字对应一个entry point。根据插件命名空间和名字,定位到单独插件
stevedore.driver.DriverManager(namespace, name, invoke_on_load, invoke_args=(), invoke_kwds={})
namespace: 命名空间
name: 插件名称
invoke_on_load:如果为True,表示会实例化该插件的类
invoke_args:调用插件对象时传入的位置参数
invoke_kwds:传入的字典参数
2) stevedore.hook.HookManager :一个名字对应多个entry point,根据给定命名空间和名字,加载名字对应的多个插件
3) stevedore.extension.ExtensionManager: 多个名字,多个entry point。给定命名空间,加载该命名空间所有插件
4 插件格式
命名空间 =
插件名称=模块:可导入对象
插件例子:
ceilometer.compute.virt =
libvirt = ceilometer.compute.virt.libvirt.inspector:LibvirtInspector
hyperv = ceilometer.compute.virt.hyperv.inspector:HyperVInspector
解释:
ceilometer.compute.virt 是命名空间
libvirt 是插件名称
ceilometer.compute.virt.libvirt.inspector 是模块
LibvirtInspector 是可导入对象,实际是一个类
5 使用插件步骤:
1) 使用abc模块创建抽象基类定义插件API行为
2) 通过继承基类并实现必要的方法来创建插件
3) 为每个api定义一个命名空间,可将应用,API名字结合起来
例如: people.say
4) setup.py中注册:
例如:
from setuptools import find_packages,setup
setup(
name='mm_stevedore_test',
version='1.0',
packages=find_packages(),
entry_points={
'people.say': [
'chinese = people.chinese:Chinese',
'english = people.english:English'
],
}
)
5) 安装插件所在模块,即在插件代码下执行如下命令:
python setup.py install
6)调用插件:
编写一个.py文件,该.py文件可以不和插件模块代码在同一目录,调用形式类似如下
mgr = stevedore.driver.DriverManager(
namespace='people.say',
name='chinese',
invoke_on_load=True,
invoke_args=(),
)
result = mgr.driver.say()
总结:
stevedore库可以用于注册插件,之所以使用插件是因为可以使自己的程序可以调用别人编写的代码,进行解藕。插件格式是:
命名空间=
插件名称=模块:可导入对象
参考:
[1] http://blog.csdn.net/gqtcgq/article/details/49620279
[2] https://blog.csdn.net/happyanger6/article/details/54798870
'''
def process():
mgr = driver.DriverManager(
namespace='people.say',
name='chinese',
invoke_on_load=True,
invoke_args=(),
)
# 注意: 这里调用的是 mgr.driver.say() 而不是 mgr.say(),不要漏了driver
result = mgr.driver.say()
print result
if __name__ == "__main__":
process()
摘自:
https://www.cnblogs.com/gqtcgq/p/7247099.html
https://blog.csdn.net/qingyuanluofeng/article/details/83536546
https://blog.csdn.net/Bill_Xiang_/article/details/78852717