教你一招 | 用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编写一个具有键盘记录.截屏以及通信功能 ...
随机推荐
- 设计模式之组合模式——Java语言描述
组合模式又叫做部分整体模式,适用于把一组相似的对象当作一个单一的对象.组合模式依据树形结构来组合对象,用来表示部分以及整体层次 介绍 意图 将对象组合成树形结构以表示"部分-整体" ...
- Ubuntu 18.04编译AOSP源码
总结一下我从Ubuntu 18.04下编译成功AOSP的过程,以及遇到的坑 我的电脑配置: OS: Ubuntu 18.04 bionic Kernel: x86_64 Linux 4.18.0-17 ...
- Java 10 var关键字详解和示例教程
在本文中,我将通过示例介绍新的Java SE 10特性——“var”类型.你将学习如何在代码中正确使用它,以及在什么情况下不能使用它. 介绍 Java 10引入了一个闪亮的新功能:局部变量类型推断.对 ...
- Python-网络爬虫模块-requests模块之请求
Python原生库urllib库不太方便使用,本着"人生苦短, 我用Python"的珍惜生命精神, 基于urllib, 开发了一个对人类来说, 更好使用的一个库——requests ...
- ZooKeeper 03 - ZooKeeper集群的脑裂问题 (Split Brain问题)
目录 1 ZooKeeper的主从机制 2 什么是ZooKeeper的脑裂 2.1 脑裂现象的表现 2.2 为什么会出现脑裂 3 ZooKeeper如何解决"脑裂" 3.1 3种可 ...
- ES 07 - Elasticsearch查询文档的六种方法
目录 1 Query String Search(查询串检索) 2 Query DSL(ES特定语法检索) 3 Query Filter(过滤检索) 4 Full Text Search(全文检索) ...
- Asp.Net Core中服务的生命周期选项区别和用法
在做一个小的Demo中,在一个界面上两次调用视图组件,并且在视图组件中都调用了数据库查询,结果发现,一直报错,将两个视图组件的调用分离,单独进行,却又是正常的,寻找一番,发现是配置依赖注入服务时,对于 ...
- springcloud~服务注册与发现Eureka的使用
服务注册与发现是微服务里的概念,也是一个基本的组件,负责服务组件的认证,即实现『你是谁』的功能,在服务注册与发现里,存在两种模式,即服务端发现和客户端发现,咱们今天说的eureka属于客户端发现! 下 ...
- Windows系统pip安装whl包
1.确保PIP的存在 2.CMD命令进入C:\Python34\Scripts里面后再执行PIP命令安装pip install wheel # D: 和cd 地址 3.把文件最好放在\S ...
- 改行了,学C#
C#数组: 定义方法 ]; ,]; //这个是二维数组 只有这一种定义方法,不像java有多种定义方法.等号前面在栈中初始化类型为一维数组类型(int[])或二维数组类型(int[,])的存储堆中地址 ...