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

作者:蓝绿色~菠菜 时间:2023-10-31 16:48:39 

使用标准库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:

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

方式二写法得到的plugins:

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

来源:https://blog.csdn.net/bocai_xiaodaidai/article/details/125971394

标签:Python,pkg,resources,动态,加载,插件
0
投稿

猜你喜欢

  • Dreamweaver使用快技法十三则

    2009-07-21 12:45:00
  • 设计MySQL数据库的技巧

    2009-09-06 11:56:00
  • PHP中的排序函数sort、asort、rsort、krsort、ksort区别分析

    2023-11-23 11:50:55
  • IE下绝对定位的元素不能响应鼠标的bug修正

    2008-09-10 13:03:00
  • 用SQL语句生成带有小计合计的数据集脚本

    2009-01-06 11:33:00
  • Python基于TensorFlow接口实现深度学习神经网络回归

    2022-07-17 22:38:28
  • Python安装及Pycharm安装使用教程图解

    2023-08-01 05:38:06
  • Python高级编程之继承问题详解(super与mro)

    2023-08-08 18:58:47
  • python使用分治法实现求解最大值的方法

    2021-07-14 12:06:09
  • 彻底终结浏览器Cache页面的解决方案

    2008-04-21 15:10:00
  • 如何用Sleep函数编译一个定时组件?

    2010-06-13 14:35:00
  • python小程序之4名牌手洗牌发牌问题解析

    2023-08-28 04:06:20
  • JS实现淘宝支付宝网站的控制台菜单效果

    2023-07-22 22:26:12
  • Mootools 1.2教程(19)——Tooltips

    2008-12-25 13:26:00
  • php基于curl实现随机ip地址抓取内容的方法

    2023-11-14 22:29:45
  • Python源码学习之PyObject和PyTypeObject

    2023-08-11 10:28:45
  • HTML5拿什么取代Flash?

    2010-05-10 20:37:00
  • 使用 iframe 获取网页片段的一个好处

    2008-12-01 12:37:00
  • ajax返回中文乱码问题解决

    2009-04-13 16:07:00
  • 微软建议的ASP性能优化28条守则(4)

    2008-02-26 17:57:00
  • asp之家 网络编程 m.aspxhome.com