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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #!/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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #!/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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!/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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #!/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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #!/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'}
作者:授客
微信/QQ:1033553122
全国软件测试QQ交流群:7156436
Git地址:https://gitee.com/ishouke
友情提示:限于时间仓促,文中可能存在错误,欢迎指正、评论!
作者五行缺钱,如果觉得文章对您有帮助,请扫描下边的二维码打赏作者,金额随意,您的支持将是我继续创作的源动力,打赏后如有任何疑问,请联系我!!!
微信打赏
支付宝打赏 全国软件测试交流QQ群
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
2019-01-01 Tomcat 参数配置相关