教你一招 | 用Python实现简易可拓展的规则引擎
做这个规则引擎的初衷是用来实现一个可序列号为json,容易拓展的条件执行引擎,用在类似工作流的场景中,最终实现的效果希望是这样的:
简单整理下需求
- 执行结果最终返回=true= or false
- 支持四则运算,逻辑运算以及自定义函数等
- 支持多级规则组合,级别理论上无限(Python递归调用深度限制)
- 序列化成json
实现
json没有条件判断和流程控制,且不可引用对象,是不好序列化规则的,除非用树来保存,但这样又过于臃肿不好阅读。
在苦苦思索的时候,突然灵光一闪~曾经我用过一个自动装机系统—razor,
它使用一种tag语法来匹配机器并打标签,他的语法是这样的:
["or",
["=", ["fact", "macaddress"], "de:ea:db:ee:f0:00"]
["=", ["fact", "macaddress"], "de:ea:db:ee:f0:01"]]
这表示匹配目标机器的Mac地址等于=de:ea:db:ee:f0:00=或=de:ea:db:ee:f0:00=,这种表达既简洁,又足够灵活这种灵活体现在理论上可以无限嵌套,也可以随意自定义操作函数(这里的=、fact)
这灵感来自于古老的=Lisp=,完全可以实现我们的想法~并且简单、好用,还非常非常灵活!就它了!
因此我就使用这种基于=Json Array=的语法来实现我们的规则引擎。
最后实现的语法规则是这样的:
规则语法 基本语法: [“操作符”, “参数1”, “参数2”, …]
多条判断语句可组合,如:
["操作符",
["操作符1", "参数1", "参数2", ...],["操作符2", "参数1", "参数2", ...]
]
["and",
[">", 0 , 0.05],
[">", 3, 2]
]
支持的操作符: 比较运算符:
=, !=, >, <, >=, <=
逻辑运算符:
and, or, not, in
四则运算:
+, -, *, /
数据转换:
int, str, upper, lower
其他特殊操作符:
可自定义操作符,例如get,从某http服务获取数据
代码
class RuleParser(object):
def __init__(self, rule):
if isinstance(rule, basestring):
self.rule = json.loads(rule)
else:
self.rule = rule
self.validate(self.rule)
class Functions(object):
ALIAS = {
'=': 'eq',
'!=': 'neq',
'>': 'gt',
'>=': 'gte',
'<': 'lt',
'<=': 'lte',
'and': 'and_',
'in': 'in_',
'or': 'or_',
'not': 'not_',
'str': 'str_',
'int': 'int_',
'+': 'plus',
'-': 'minus',
'*': 'multiply',
'/': 'divide'
}
def eq(self, *args):
return args[0] == args[1]
def neq(self, *args):
return args[0] != args[1]
def in_(self, *args):
return args[0] in args[1:]
def gt(self, *args):
return args[0] > args[1]
def gte(self, *args):
return args[0] >= args[1]
def lt(self, *args):
return args[0] < args[1]
def lte(self, *args):
return args[0] <= args[1]
def not_(self, *args):
return not args[0]
def or_(self, *args):
return any(args)
def and_(self, *args):
return all(args)
def int_(self, *args):
return int(args[0])
def str_(self, *args):
return unicode(args[0])
def upper(self, *args):
return args[0].upper()
def lower(self, *args):
return args[0].lower()
def plus(self, *args):
return sum(args)
def minus(self, *args):
return args[0] - args[1]
def multiply(self, *args):
return args[0] * args[1]
def divide(self, *args):
return float(args[0]) / float(args[1])
def abs(self, *args):
return abs(args[0])
@staticmethod
def validate(rule):
if not isinstance(rule, list):
raise RuleEvaluationError('Rule must be a list, got {}'.format(type(rule)))
if len(rule) < 2:
raise RuleEvaluationError('Must have at least one argument.')
def _evaluate(self, rule, fns):
"""
递归执行list内容
"""
def _recurse_eval(arg):
if isinstance(arg, list):
return self._evaluate(arg, fns)
else:
return arg
r = map(_recurse_eval, rule)
r[0] = self.Functions.ALIAS.get(r[0]) or r[0]
func = getattr(fns, r[0])
return func(*r[1:])
def evaluate(self):
fns = self.Functions()
ret = self._evaluate(self.rule, fns)
if not isinstance(ret, bool):
logger.warn('In common usage, a rule must return a bool value,'
'but get {}, please check the rule to ensure it is true' )
return ret
解析
这里Functions这个类,就是用来存放操作符方法的,由于有些操作符不是合法的Python变量名,所以需要用ALIAS做一次转换。
当需要添加新的操作,只需在Functions中添加方法即可。由于始终使用array来存储,所以方法接收的参数始终可以用args[n]来访问到,这里没有做异常处理,如果想要更健壮的话可以拓展validate方法,以及在每次调用前检查参数。
整个规则引擎的核心代码其实就是=~evaluate~=这个10行不到的方法,在这里会递归遍历列表,从最里层的列表开始执行,然后层层往外执行,最后执行完毕返回一个Boolean值,当然这里也可以拓展改成允许返回任何值,然后根据返回值来决定后续走向,这便可以成为一个工作流中的条件节点了。
结束语
东西简单粗陋,希望能给大家带来一些帮助或者一些启发~
原文链接:https://yq.aliyun.com/articles/2631?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&utm_content=m_8867
- [OIDC in Action] 1. 基于OIDC(OpenID Connect)的SSO
- [OIDC in Action] 2. 基于OIDC(OpenID Connect)的SSO(纯JS客户端)
- [认证授权] 2.OAuth2授权(续) & JWT(JSON Web Token)
- [Cake] 0.C#Make自动化构建-简介
- [认证授权] 3.基于OAuth2的认证(译)
- [Asp.Net Core] 1. IIS中的 Asp.Net Core 和 dotnet watch
- kafka数据迁移实践
- HDFS 2.x 磁盘间数据均衡的一种可行办法
- Batik渲染png图片异常的bug修复全程记录
- Web应用服务器安全:攻击、防护与检测
- 基于Go Packet实现网络数据包的捕获与分析
- 动态追踪技术(四):基于 Linux bcc/BPF 实现 Go 程序动态追踪
- Hive 时间转换函数使用心得
- Flume-Hbase-Sink针对不同版本flume与HBase的适配研究与经验总结
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【Java面试总结】多线程
- RL实践1——动态规划值迭代
- RL实践3——为Agent添加Policy
- CentOS 快速安装Python3和pip3
- Linux使用 常见经验和技巧总结
- SwiftUI:辅助功能——简单介绍
- Xshell突出显示集
- samba服务安装脚本
- 【Vue.js】Vue.js组件库Element中的树形控件、分页、标记、头像和警告
- 磁盘分区空间检测工具
- 【Vue.js】Vue.js组件库Element中的加载、消息提示、弹框和通知
- 云主机装ikuai
- 【Vue.js】Vue.js组件库Element中的导航菜单、标签页、面包屑和页头
- CentOS7查看硬盘是SSD还是HDD
- 浅谈CentOS防火墙命令