教你一招 | 用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值,当然这里也可以拓展改成允许返回任何值,然后根据返回值来决定后续走向,这便可以成为一个工作流中的条件节点了。
结束语
东西简单粗陋,希望能给大家带来一些帮助或者一些启发~
教你一招 | 用Python实现简易可拓展的规则引擎的更多相关文章
- 【转载】教你分分钟学会用python爬虫框架Scrapy爬取心目中的女神
原文:教你分分钟学会用python爬虫框架Scrapy爬取心目中的女神 本博文将带领你从入门到精通爬虫框架Scrapy,最终具备爬取任何网页的数据的能力.本文以校花网为例进行爬取,校花网:http:/ ...
- IE-“无法浏览网页” 教你十招解决疑难杂症
“无法浏览网页” 教你十招解决疑难杂症 相信大家也有遇到过像IE不能上网浏览的问题.下面就来给大家介绍一下常见原因和解决方法: 一.网络设置的问题 这种原因比较多出现在需要手动指定IP.网关.DNS服 ...
- 教你一招 - 如何安装nopcommerce2.5
教你一招 - 如何安装nopcommerce2.5 29. 五月 2012 16:22 / wcf / 教你一招 . 解决方案 ...
- 文章如何做伪原创 SEO大神教你几招做"原创"网站文章的心得
想要创作出好的文章并被百度所喜欢,就非常需要SEO的优化能力,以及要对文章进行塬创或伪塬创,那么,如何做伪塬创文章?以及如何做好塬创网站文章呢?对此,本文小编就为大家带来了几招做"塬创&qu ...
- python搭建简易服务器实例参考
有关python搭建简易服务器的方法. 需求分析: 省油宝用户数 已经破了6000,原有的静态报表 已经变得臃肿不堪, 每次打开都要缓上半天,甚至浏览器直接挂掉 采用python搭建一个最最简易的 w ...
- PDF怎么替换页面,教你一招秒实现
PDF格式是在办公中比较常用的文件格式之一,虽然很好用,也很容易携带,但也容易出现一个问题,当你想要对PDF文件操作或者修改的时候,才发现PDF文件不是那么容易就能进行编辑和修改的,特别是需要对PDF ...
- 线上Bug无法复现怎么办?老司机教你一招,SpringBoot远程调试不用愁!
前言 在部署线上项目时,相信大家都会遇到一个问题,线上的 Bug 但是在本地不会复现,多么无奈. 此时最常用的就是取到前端传递的数据用接口测试工具测试,比如 POSTMAN,复杂不,难受不? 今天陈某 ...
- 教你用OpenCV 和 Python给证件照换底色(蓝底 <->红底->白底)
在我们的生活中常常要用到各种底色要求的证件电子照,红底.蓝底.或者白底,而假如你手上只有一种底色的证件照,你又不想再去拍又不会PS怎么办?今天教你们用OpenCV和Python给你的证件照换底色. P ...
- Python编写简易木马程序(转载乌云)
Python编写简易木马程序 light · 2015/01/26 10:07 0x00 准备 文章内容仅供学习研究.切勿用于非法用途! 这次我们使用Python编写一个具有键盘记录.截屏以及通信功能 ...
随机推荐
- C#语言介绍
C#(读作“See Sharp”)是一种简单易用的新式编程语言,不仅面向对象,还类型安全. C# 源于 C 语言系列,C.C++.Java 和 JavaScript 程序员很快就可以上手使用. C# ...
- 软件开发架构介绍||OSI七层协议之物理层、数据链路层、网络层、传输层(mac地址、ip协议、断开协议、tcp协议之三次握手四次挥手)
一.网络编程 软件开发架构 C/S架构 C:客户端 想体验服务的时候才会去找服务端体验服务 S:服务端 24小时不间断的提供服务,即时监听,随时待命 B/S架构 B:浏览器 想体验服务的时候 ...
- JavaScript函数定义 ,参数调用
一.JavaScript函数函数: 函数就是一种封装,由事件驱动的或者当它被调用时执行的可重复使用的代码块.定义函数:function 函数名(){函数体;}数不会自动执行,需要被调用才可以执行函数名 ...
- Api管家系列(二):编辑和继承Class
上篇写了个大概,今天我详细说一下参数的编辑,废话不多说 先打开一个项目,我要特别说一下设置里的“默认参数设置” 打开默认参数设置,这里我用红色圈出的tab可以设置请求头,返回头和返回状态,这些设置会在 ...
- Ftp修改为主被动模式命令
FTP是有两种数据连接模式的,主动模式和被动模式. PORT(主动)方式:客户端向服务器的FTP端口(默认是21)发送连接请求,服务器接受连接,建立一条命令链路.当需要传送数据时,客户端在命令链路上用 ...
- 搭建环境-Monkeyrunner-自动化测试工具
这篇博客帮助挺大,我补充部分,帮助同样的小白哈哈,侵删 https://www.cnblogs.com/lynn-li/p/5885001.html 1.前期准备 需要安装:JDK,SDK,pytho ...
- 远程连接身份验证错误,又找不到加密Oracle修正
远程连接服务器出现了错误,错误信息为:远程连接身份验证错误,又找不到加密Oracle修正. 服务器系统:Windows Server2016 客户端系统:Windows10家庭版和专业版 出错原因 ...
- x宝23大洋包邮的老式大朝华MP3播放器简单评测
(纯兴趣测评,非广告) 最近逛X宝,看到了这个古董级MP3播放器居然还在售,于是脑抽+情怀泛滥买了一个. 然后呢,从遥远的深圳跨越好几千公里邮过来了这个玩意: 那节南孚5号电池是我自己的,是为了对比一 ...
- REST API disable / enable service auto start by API
how to disable service auto start by API as the following how to enable service auto start by API as ...
- CMake简介
目录 一.CMake简介 二.CMake典型示例 源代码 demo.cpp cmake脚本 CMakeLists.txt 编译流程 三.CMake常用命令 常用命令介绍 设置编译目标类型 指定编译包含 ...