教你一招 | 用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编写一个具有键盘记录.截屏以及通信功能 ...
随机推荐
- redis增删查改数据Util
目录 (1)需要导入的包 (2)redis配置文件 (3)RedisUtil类 (1)需要导入的包 <dependency> <groupId>org.springframew ...
- Sharepoint 2013内容查询Web部件自定义显示样式(实战)
分享人:广州华软 星尘 一. 前言 在进行Sharepoint开发时,经常会遇到内容展示个性化需求的问题,当然如果通过自定义开发控件对于内容展示的需求基本都可以很好的解决,但自定义开发也有不好的地方, ...
- grep 及正则表达式
grpe 及正则表达式 文本查找的需要:grep,egrep,fgrepgrep: 根据模式,搜索文本,并将符合模式的文本行显示出来.Pattern : 文本字符以及正则表达式的元字符组合而成的匹配条 ...
- python 简史
---恢复内容开始--- Python的作者,Guido von Rossum,确实是荷兰人.1982年,Guido从阿姆斯特丹大学(University of Amsterdam)获得了数学和计算机 ...
- Demo更新列表
Sdk 对应的demo ESF (1)ESF/ESFramework.EntranceDemo.rar (2)ESF/4.ESFramework.Demos.Ftp.rar (3)ESF/6.ESFr ...
- C# 三种打印方式含代码
一:C#代码直接打印pdf文件(打印质保书pdf文件) 引用: 代码注释很详细了. private void btn_pdf_Click(object sender, RoutedEventArgs ...
- 如何接入银联“快速接入”产品API
引言:使用银联开放平台的用户或多或少都接触过产品API吧,那么大家对于“快速接入”产品API是否还会存在一些疑问呢?因为我之前对“快速接入”模糊不清,所以整理的一份详细的资料,里面梳理了“快速接入”产 ...
- AI - TensorFlow - 示例02:影评文本分类
影评文本分类 文本分类(Text classification):https://www.tensorflow.org/tutorials/keras/basic_text_classificatio ...
- Spring之旅第六篇-事务管理
一.什么是事务 什么是事务(Transaction)?事务是数据库中的概念,是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit). 有个非常经典的转账问题:A向B转款1000元,A转出成 ...
- JavaScript夯实基础系列(五):类
JavaScript中没有类,是通过使用构造函数和原型模式的组合来实现类似其它面向对象编程语言中"类"的功能.ES6引入的关键字class,形式上向其它面向对象编程语言靠拢,其 ...