Python 插件式程序设计与开发实践总结
插件式程序设计与开发实践总结
By:授客 QQ:1033553122
开发环境
win 10
python 3.6.5
代码结构
需求描述
如上,以user.py为程序入口脚本,运行该脚本时,需要创建一个user类对象,执行一系列动作(包含一系列动作的列表)。程序执行动作前,要求先获取动作名称,根据该名称,执行不同的操作。这些操作,对应不同的类函数。
实现思路
大致实现思路就是,把user对象需要运行的类函数(使用@classmethod修饰的函数,可不用创建对象进行调用),当作插件函数,并设置为user的属性,这样程序运行时,可通过该属性来调用对应的类函数。这里的问题是,程序怎么知道执行哪个类函数呢?到目前为止,程序只能根据动作名称来判断待执行的操作,所以,需要建立动作名称和类函数的映射关系。
怎么建立动作名称和类函数的映射关系呢?这里用到了装饰器,新建一个装饰器类ActionDecorator,为该类设置一个字典类型的类属性ACTION_FUNC_CLASS_MODULE_MAP,用这个类来存放动作名称和类函数的映射关系。我们把需要当作插件函数的类函数都用该装饰器进行修饰。
这里,笔者发现一个特性,就是对应模块被导入时,对应模块,对应类函数如果使用了装饰器,该装饰器函数会被执行。同时,笔者还发现另外一个特性,
首次对某个包执行import操作时,该包下面的__init__.py文件将优先被执行。基于这两个特性,我们把装饰器放在用于管理插件类函数的外围软件包下(例中的components包),同时,在该外围软件包下的__init__.py中加入动态加载插件模块的代码:遍历外围软件包下的所有非__init__.py文件,并且动态加载改模块。这样,当在user.py入口文件中,执行from components.decoraters.action_decorater import ActionDecorator时,会自动执行components/__init__.py文件,动态加载所有插件模块,并且自动触发装饰器的执行,装饰器方法执行,会自动根据提供的方法参数建立动作名称和类函数的映射关系。
然后,在初始化user对象时,给该对象动态设置属性,属性名称设置为动作名称,属性值设置为类方法,这样,执行动作时,就可以根据动作名称调用对应的类方法了。
代码实现
action_decorate.py
#!/usr/bin/env python
# -*- coding:utf-8 -*- '''
@CreateTime: 2020/12/09 14:58
@Author : shouke
''' class ActionDecorator(object):
'''
action 装饰器
''' ACTION_FUNC_CLASS_MODULE_MAP = {} @classmethod
def action_register(cls, action, class_name, function_name, module_path):
def wrapper(func):
cls.ACTION_FUNC_CLASS_MODULE_MAP.update({action: {'class_name':class_name, 'function_name':function_name, 'module_path':module_path}})
return func
return wrapper
components/__init__.py
#!/usr/bin/env python
# -*- coding:utf-8 -*- '''
@Author : shouke
''' import os.path
import importlib def load_plugin_modules():
'''递归加载当前目录下的所有模块''' head, tail = os.path.split(__file__)
package_father_path, package = os.path.split(head) def load_modules(dir_path):
nonlocal package_father_path
if not os.path.isdir(dir_path):
return
for name in os.listdir(dir_path):
full_path = os.path.join(dir_path, name)
if os.path.isdir(full_path):
load_modules(full_path)
elif not name.startswith('_') and name.endswith('.py'):
temp_path = full_path.replace(package_father_path, '')
relative_path = temp_path.replace('\\', '/').lstrip('/').replace('/', '.')
importlib.import_module(relative_path.rstrip('.py'), package=package)
load_modules(head) # 加载模块,自动触发装饰器,获取相关插件函数相关信息 load_plugin_modules()
assertioner.py
#!/usr/bin/env python
# -*- coding:utf-8 -*- '''
@Author : shouke
''' from components.decoraters.action_decorater import ActionDecorator class Assertioner(object):
@classmethod
@ActionDecorator.action_register('assert_equal', 'Assertioner', 'assert_equal', __name__)
def assert_equal(self, config:dict, *args, **kwargs):
print('执行断言')
print('断言配置:\n', config)
send_request.py
#!/usr/bin/env python
# -*- coding:utf-8 -*- '''
@Author : shouke
''' from components.decoraters.action_decorater import ActionDecorator class Requester(object):
@ActionDecorator.action_register('send_request', 'Requester', 'send_request', __name__)
@classmethod
def send_request(self, config:dict, *args, **kwargs):
print('发送请求')
print('请求配置:')
print(config)
example.py
#!/usr/bin/env python
# -*- coding:utf-8 -*- '''
@CreateTime: 2020/12/10 15:51
@Author : shouke
''' from components.decoraters.action_decorater import ActionDecorator class CustomClassName(object):
@ActionDecorator.action_register('custom_action_name', 'CustomClassName', 'action_func_name', __name__)
@classmethod
def action_func_name(self, config:dict, *args, **kwargs):
'''
example
user_instance: kwargs['user'] # 压测用户实例
''' # do something you want # 说明 plugings目录下可自由创建python包,管理插件,当然,也可以位于components包下其它任意位置创建python包,管理插件(不推荐)
user.py
#!/usr/bin/env python
# -*- coding:utf-8 -*- '''
@Author : shouke
''' from components.decoraters.action_decorater import ActionDecorator class User(object): def __init__(self):
for action, action_map in ActionDecorator.ACTION_FUNC_CLASS_MODULE_MAP.items():
module = __import__(action_map.get('module_path'), fromlist=['True'])
class_cls = getattr(module, action_map.get('class_name'))
setattr(self, action, getattr(class_cls, action_map.get('function_name'))) def run_actions(self, actions):
''' 执行一系列动作 ''' for step in actions:
action = step.get('action') if hasattr(self, action):
getattr(self, action)(step, user=self) if __name__ == '__main__':
actions = [{
"action": "send_request",
"name": "请求登录", #可选配置,默认为None
"method": "POST",
"path": "/api/v1/login",
"body": {
"account": "shouke",
"password": "123456"
},
"headers": {
"Content-Type": "application/json"
}
},
{
"action": "assert_equal",
"name": "请求响应断言",
"target": "body",
"rule": "assert_contain",
"patterns": ["shouke","token"],
"logic":"or"
}]
User().run_actions(actions)
运行结果
发送请求
请求配置:
{'action': 'send_request', 'name': '请求登录', 'method': 'POST', 'path': '/api/v1/login', 'body': {'account': 'shouke', 'password': '123456'}, 'headers': {'Content-Type': 'application/json'}}
执行断言
断言配置:
{'action': 'assert_equal', 'name': '请求响应断言', 'target': 'body', 'rule': 'assert_contain', 'patterns': ['shouke', 'token'], 'logic': 'or'}
Python 插件式程序设计与开发实践总结的更多相关文章
- 响应式WEB页面开发实践
转自:https://github.com/markyun/My-blog/issues/27 最近得到一个新任务单,让我用一套页面适应所有主流终端(Android.iPhone.iPad.PC),而 ...
- Chrome插件(Extensions)开发实践
内容摘自:http://www.cnblogs.com/mfryf/p/3701801.html
- WebApi 插件式构建方案:发现并加载程序集
插件式的 WebApi 开发,首要面对的问题就是程序集的发现.因为开发的过程中,都是在各自的解决方案下进行开发,部署后是分模块放在一个整体的的运行时网站下. 约定 这里我根据上一节的设定,把插件打包完 ...
- 《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(上)
目录 前言 第1章 安装 第2章 程序的基本结构 第3章 模板 第4章 Web表单 第5章 数据库 第6章 电子邮件 第7章 大型程序的结构 前言 学习Python也有一个半月时间了,学到现在感觉 ...
- 基于Flask的Web应用程序插件式结构开发
事实上,很多应用程序基于插件式结构开发,可以很方便了扩展软件的功能,并且这些功能完全可以依托于第三方开发者,只要提供好接口和完备文档,比如wordpress.谷歌火狐浏览器等. Python这样的动态 ...
- 也来学学插件式开发续-利用MEF
前面一个博客:也来学学插件式开发中很多朋友留言说可以用MEF来实现.于是我就试着用MEF实现了一下. 步骤和上一篇差不多,只是加载插件的方式有所不同.这只是一个自己的示例程序,肯定有很多不足之处,欢迎 ...
- MEF 插件式开发 - 小试牛刀
原文:MEF 插件式开发 - 小试牛刀 目录 MEF 简介 实践出真知 面向接口编程 控制反转(IOC) 构建入门级 MEF 相关参考 MEF 简介 Managed Extensibility Fra ...
- 从零开始实现ASP.NET Core MVC的插件式开发(七) - 近期问题汇总及部分解决方案
标题:从零开始实现ASP.NET Core MVC的插件式开发(七) - 问题汇总及部分解决方案 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/12 ...
- AppDomain实现【插件式】开发
前言: 近期项目中需要实现"热插拔"式的插件程序,例如:定义一个插件接口:由不同开发人员实现具体的插件功能类库:并最终在应用中调用具体插件功能. 此时需要考虑:插件执行的安全性(隔 ...
- Redis的Python实践,以及四中常用应用场景详解——学习董伟明老师的《Python Web开发实践》
首先,简单介绍:Redis是一个基于内存的键值对存储系统,常用作数据库.缓存和消息代理. 支持:字符串,字典,列表,集合,有序集合,位图(bitmaps),地理位置,HyperLogLog等多种数据结 ...
随机推荐
- 做程序员这么久,你知道UTF-8和Unicode的关系吗?
UTF-8和Unicode到底有什么区别?是存储方式不同?编码方式不同?它们看起来似乎很相似,但是实际上他们并不是同一个层次的概念. 要想先讲清楚他们的区别,首先应该讲讲Unicode的来由: 众所周 ...
- Android 12(S) MultiMedia Learning(五)NuPlayer
之前几节看到MediaPlayer的java接口最终会调用到native层中的NuPlayer,NuPlayer由Render,DecoderBase,Source三部分组成. Render:负责AV ...
- 微服务新体验之Aspire初体验
安装aspire 查看vs版本 我这的版本是17.9.7,不支持aspire,所以需要升级 更新VS 点击 帮助->检查更新 点击更新 静等安装升级 创建aspire项目 项目创建成功,如下图 ...
- v-once指令 v-if和v-show
v-once指令 只渲染元素和组件一次,之后元素和组件将失去响应式功能 v-if和v-show 根据表达式的布尔值(true/false)进行判断是否渲染该元素 注:v-if 有更高的切换开销,而 v ...
- INFINI Labs 产品更新 | Easysearch 1.8.0 发布数据写入限流功能
INFINI Labs 产品又更新啦~,包括 Easysearch v1.8.0.Gateway.Console.Agent.Loadgen v1.25.0.本次各产品更新了很多亮点功能,如 Easy ...
- 使用 Java 客户端通过 HTTPS 连接到 Easysearch
Easysearch 一直致力于提高易用性,这也是我们的核心宗旨,然而之前一直没有官方的 Java 客户端,也对用户使用造成了一些困扰,现在,我们正式发布了第一个 Java 客户端 Easysearc ...
- 使用 JavaScript 脚本来进行复杂的查询改写
有这么一个需求: 网关里怎样对跨集群搜索进行支持的呢?我想实现: 输入的搜索请求是 lp:9200/index1/_search 这个索引在 3 个集群上,需要跨集群检索,也就是网关能否改成 lp:9 ...
- sshd服务部署
sshd服务部署 软件安装修改配置文件启动使用 1.搭建所有服务的套路 关闭防火墙和selinux(实验环境都先关闭掉) 配置yum源(公网源或者本地源) 软件安装和检查 了解并修改配置文件 启动服 ...
- 项目管理--PMBOK 读书笔记(7)【项目成本管理】
1.成本术语: 2.三点估算(PERT): 平均估算值=(最可能持续时间*4+最乐观+最悲观)/6 标准差=(最乐观-最悲观)/6 68.26%.95.46%.99.73% 3.估算成本的工具:质量成 ...
- mysql ON DUPLICATE KEY UPDATE 演示
<e>查询:INSERT INTO member_test(mem_no,flag)VALUE('111','1') 错误代码: 1062Duplicate entry '111' for ...