Python pkg_resources模块动态加载插件实例分析

 更新时间:2022年08月04日 10:22:04   作者:蓝绿色~菠菜  
当编写应用软件时,我们通常希望程序具有一定的扩展性,额外的功能——甚至所有非核心的功能,都能通过插件实现,具有可插拔性。特别是使用 Python 编写的程序,由于语言本身的动态特性,为我们的插件方案提供了很多种实现方式

使用标准库importlibimport_module()函数、django的import_string(),它们都可以动态加载指定的 Python 模块。

举两个动态加载例子:

举例一:

在你项目中有个test函数,位于your_project/demo/test.py中,那么你可以使用import_module来动态加载并调用这个函数而不需要在使用的地方通过import导入。

module_path = 'your_project/demo'
module = import_module(module_path)
module.test()

举例二:

django的中间件都用过吧,只需要在setting中配置好django就能自动被调用,这也是利用import_string动态加载的。

#settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
     ...
]
# 动态加载调用处代码
for middleware_path in reversed(settings.MIDDLEWARE):
    middleware = import_string(middleware_path)
    ...

以上方式会有一些缺点:

  • 所引用模块不存在时,在项目启动时不会及时抛出错误,只有在真正调用时才能被发现
  • 所引用模块要事先写好并放到指定位置,不能删
  • 半自动的可插拔性,想禁用某个插件功能需要手动修改代码。如想去掉django的SessionMiddleware功能,那需要手动去修改settings配置文件

pkg_resources实现动态加载插件

下面介绍另外一种动态 载插件的方法,与安装库setuptools一并安装的软件库pkg_resources,它基本解决了上述的问题,并且事实上成为了流行的插件实现方式。 pkg_resources操作的主要单位就是 Distribution(包分发),关于Distribution可以参考这里。Python 脚本启动时,pkg_resources识别出搜索路径中的所有 Distribution 的命名空间包,因此,我们会发现sys.path包含了很多pip安装的软件包的路径,并且可以正确执行import操作。

pkg_resources自带一个全局的WorkingSet对象,代表默认的搜索路径的工作集,也就是我们常用的sys.path工作集。有了这个工作集,那就能轻松实现动态导入任何模块了。

下面上案例:

这个案例是ansible-runner的一个事件处理插件,项目地址GitHub - ansible/ansible-runner-http。只需要把这个包安装到你的虚拟环境,ansible-runner就会自动识别并调用status_handler、event_handler两个事件处理函数。当卸载这个包后,ansible-runner就会使用默认的方式处理事件。相比前面介绍的import_module方式,这种动态加载方式好在对源代码侵入性少,实现真正的即插即用。下面分析它是怎么利用pkg_resources做到的。

ansible-runner-http项目源码目录结构:

├── README.md

├── ansible_runner_http

│├── __init__.py

│└── events.py

└── setup.py

event.py:

...
def status_handler(runner_config, data):
    plugin_config = get_configuration(runner_config)
    if plugin_config['runner_url'] is not None:
        status = send_request(plugin_config['runner_url'],
                              data=data,
                              headers=plugin_config['runner_headers'],
                              urlpath=plugin_config['runner_path'])
        logger.debug("POST Response {}".format(status))
    else:
        logger.info("HTTP Plugin Skipped")
def event_handler(runner_config, data):
    status_handler(runner_config, data)

__init__.py:

from .events import status_handler, event_handler # noqa

setup.py:

from setuptools import setup, find_packages
with open('README.md', 'r') as f:
    long_description = f.read()
setup(
    name="ansible-runner-http",
    version="1.0.0",
    author="Red Hat Ansible",
    url="https://github.com/ansible/ansible-runner-http",
    license='Apache',
    packages=find_packages(),
    long_description=long_description,
    long_description_content_type='text/markdown',
    install_requires=[
        'requests',
        'requests-unixsocket',
    ],
    #方式一:具体到某个module
    entry_points={'ansible_runner.plugins': ['http = ansible_runner_http']},
    #方式二:具体到某个module下的函数
    #entry_points={'ansible_runner.plugins': [
    #    'status_handler = ansible_runner_http:status_handler',
    #    'event_handler = ansible_runner_http:event_handler',
    #    ]
    #},
    zip_safe=False,
)

重点在setup中的entry_points选项:

  • 组名,以点号分隔便于组织层次,但与 Package 没有关联,如ansible_runner.plugin
  • 名字,如 http
  • Distribution 中的位置,可以指向一个module如ansible_runner_http。也可以指向module下某个函数如ansible_runner_http:status_handler,前面是 Module,后面是模块内的函数

这样一来一旦这个包被安装后,pkg_resources就可以动态识别这个插件了。

下面看调用方:

...
plugins = {
    #调用load方法,获取指向python的对象
    entry_point.name: entry_point.load()
    for entry_point
    #调用WorkingSet.iter_entry_points方法遍历所有EntryPoint,参数为组名
    in pkg_resources.iter_entry_points('ansible_runner.plugins')
}
...
def event_callback(self, event_data):
        '''
        Invoked for every Ansible event to collect stdout with the event data and store it for
        later use
        '''
    for plugin in plugins:
        plugins[plugin].event_handler(self.config, event_data)
        ...

方式一写法得到的plugins:

方式二写法得到的plugins:

到此这篇关于Python pkg_resources模块动态加载插件实例分析的文章就介绍到这了,更多相关Python 动态加载插件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Django与图表的数据交互的实现

    Django与图表的数据交互的实现

    本文主要介绍了Django与图表的数据交互的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • Django框架视图函数设计示例

    Django框架视图函数设计示例

    这篇文章主要介绍了Django框架视图函数设计,结合实例形式分析了Django框架视图函数处理流程、原理与相关操作注意事项,需要的朋友可以参考下
    2019-07-07
  • tensorflow 环境变量设置方式

    tensorflow 环境变量设置方式

    今天小编就为大家分享一篇tensorflow 环境变量设置方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-02-02
  • Python中的策略模式之解锁编程的新维度

    Python中的策略模式之解锁编程的新维度

    策略模式是一种设计模式,通过定义一系列算法,将它们封装起来,并且使它们可以相互替换,从而使算法的变化独立于使用算法的客户,本文给大家介绍Python中的策略模式之解锁编程的新维度,感兴趣的朋友跟随小编一起看看吧
    2024-10-10
  • Django中Cookie设置及跨域问题处理详解

    Django中Cookie设置及跨域问题处理详解

    本文主要介绍了Django中Cookie设置及跨域问题处理,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • 基于Python代码编辑器的选用(详解)

    基于Python代码编辑器的选用(详解)

    下面小编就为大家带来一篇基于Python代码编辑器的选用(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • Python管理Windows服务小脚本

    Python管理Windows服务小脚本

    这篇文章主要为大家详细介绍了Python管理Windows服务的小脚本,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • 详解Pytorch如何利用yaml定义卷积网络

    详解Pytorch如何利用yaml定义卷积网络

    大多数卷积神经网络都是直接通过写一个Model类来定义的,这样写的代码其实是比较好懂,也很方便。但是本文将介绍另一个方法:利用yaml定义卷积网络,感兴趣的可以了解一下
    2022-10-10
  • python 基于opencv操作摄像头

    python 基于opencv操作摄像头

    这篇文章主要介绍了python 基于opencv操作摄像头的方法,帮助大家更好的理解和使用python,感兴趣的朋友可以了解下
    2020-12-12
  • Python使用protobuf序列化和反序列化的实现

    Python使用protobuf序列化和反序列化的实现

    protobuf是一种二进制的序列化格式,相对于json来说体积更小,传输更快,本文主要介绍了Python使用protobuf序列化和反序列化的实现,感兴趣的可以了解一下
    2021-05-05

最新评论