做这个规则引擎的初衷是用来实现一个可序列号为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实现简易可拓展的规则引擎的更多相关文章

  1. 【转载】教你分分钟学会用python爬虫框架Scrapy爬取心目中的女神

    原文:教你分分钟学会用python爬虫框架Scrapy爬取心目中的女神 本博文将带领你从入门到精通爬虫框架Scrapy,最终具备爬取任何网页的数据的能力.本文以校花网为例进行爬取,校花网:http:/ ...

  2. IE-“无法浏览网页” 教你十招解决疑难杂症

    “无法浏览网页” 教你十招解决疑难杂症 相信大家也有遇到过像IE不能上网浏览的问题.下面就来给大家介绍一下常见原因和解决方法: 一.网络设置的问题 这种原因比较多出现在需要手动指定IP.网关.DNS服 ...

  3. 教你一招 - 如何安装nopcommerce2.5

    教你一招 - 如何安装nopcommerce2.5 29. 五月 2012 16:22         /          wcf         /          教你一招 . 解决方案    ...

  4. 文章如何做伪原创 SEO大神教你几招做"原创"网站文章的心得

    想要创作出好的文章并被百度所喜欢,就非常需要SEO的优化能力,以及要对文章进行塬创或伪塬创,那么,如何做伪塬创文章?以及如何做好塬创网站文章呢?对此,本文小编就为大家带来了几招做"塬创&qu ...

  5. python搭建简易服务器实例参考

    有关python搭建简易服务器的方法. 需求分析: 省油宝用户数 已经破了6000,原有的静态报表 已经变得臃肿不堪, 每次打开都要缓上半天,甚至浏览器直接挂掉 采用python搭建一个最最简易的 w ...

  6. PDF怎么替换页面,教你一招秒实现

    PDF格式是在办公中比较常用的文件格式之一,虽然很好用,也很容易携带,但也容易出现一个问题,当你想要对PDF文件操作或者修改的时候,才发现PDF文件不是那么容易就能进行编辑和修改的,特别是需要对PDF ...

  7. 线上Bug无法复现怎么办?老司机教你一招,SpringBoot远程调试不用愁!

    前言 在部署线上项目时,相信大家都会遇到一个问题,线上的 Bug 但是在本地不会复现,多么无奈. 此时最常用的就是取到前端传递的数据用接口测试工具测试,比如 POSTMAN,复杂不,难受不? 今天陈某 ...

  8. 教你用OpenCV 和 Python给证件照换底色(蓝底 <->红底->白底)

    在我们的生活中常常要用到各种底色要求的证件电子照,红底.蓝底.或者白底,而假如你手上只有一种底色的证件照,你又不想再去拍又不会PS怎么办?今天教你们用OpenCV和Python给你的证件照换底色. P ...

  9. Python编写简易木马程序(转载乌云)

    Python编写简易木马程序 light · 2015/01/26 10:07 0x00 准备 文章内容仅供学习研究.切勿用于非法用途! 这次我们使用Python编写一个具有键盘记录.截屏以及通信功能 ...

随机推荐

  1. Dapper 链式查询 扩展

    Dapper 链式查询扩展 DapperSqlMaker   Github地址:https://github.com/mumumutou/DapperSqlMaker  欢迎大佬加入 Demo: 查询 ...

  2. 微信小程序计算器Bug版=-=(笔记)

    微信小程序计算器BUG版本 无APPID的测试号登录,先在app.json中更改路径,以及修改头部信息. 首先一个输入框字段用{{screenData}} 功能可以退格,清屏,正负号,正常操作加减乘除 ...

  3. 全新定义!免费开源ERP平台如何玩转工业互联网

    简述 IoT Box通过Wifi.蓝牙.USB.网线等方式连接设备.IoT再通过互联网连接到Odoo服务器 Odoo的各种应用通过IoT操作各种设备.例如,PoS应用通过IoT操作小票打印机.银行刷卡 ...

  4. android 自定义权限管理

    在Android6.0后有些权限就需要进行询问,虽然可以将targetSdkVersion设置成小于等于23,但是这样可能有些东西无法使用,所以要进行权限的管理. 实现逻辑:打开页面就询问权限,如果没 ...

  5. ASP.NET Aries 高级开发教程:Excel导入之单表配置(上)

    前言: 随着ASP.NET Aries的普及,刚好也有点闲空,赶紧把Excel导入功能的教程补上. Excel导入功能,分为四篇:单表配置(上).多表高级配置(中).配置规则(下).代码编写(番外篇) ...

  6. 你可能需要为你的 APP 适配 iOS 11

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/AZFrqL9dnlgA6Vt2sVhxIw 作者:s ...

  7. C#常见金额优选类型及其三种常用的取整方式

    这两天一直在做一个商城后台的对账方面的工作,忽然发现C#真的有很多值的学习的东西: 一.C#常用的三种取整方式(主要适用于double.decimal.float这一类型的数据): Math.Roun ...

  8. 如何取消-"插入耳机自动显示提示框"

    首先我们打开控制面板->1,你可以直接搜索控制面板打开  2,你可以右击我的电脑->点击属性->左上角打开控制面板

  9. 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 二九║ Nuxt实战:异步实现数据双端渲染

    回顾 哈喽大家好!又是元气满满的周~~~二哈哈,不知道大家中秋节过的如何,马上又是国庆节了,博主我将通过三天的时间,给大家把项目二的数据添上(这里强调下,填充数据不是最重要的,最重要的是要配合着让大家 ...

  10. IdentityServer4 中文文档与实战

    写在前面 写于2018.9.12 我研究 IdentityServer4 是从.net core 1.1的时候开始的,那时候国内的中文资料比较少,我都是按照官方文档来研究的,整理成了笔记.这个系列文档 ...