按规则解析字符串中的嵌套函数并实现函数调用

需求

1、按照一定规则解析字符串中的函数表达式,并替换这些表达式。这些函数表达式可能包含其它函数表达式,即支持函数嵌套

2、函数表达式格式:${ __函数名称() }、${__函数名称( 函数参数 )}

注意:

  1. 函数名称以_打头
  2. 函数参数之间使用 || 分隔 形如 ${ __function1( "str_value" || 123456 || 'test' )}
  3. ${ 之间不能有空格
  4. 函数名称和函数的左括号 ( 之间不能有空隔
  5. 函数支持嵌套,形如:${ __function1( ${__function2()} )}
  6. 函数参数如果是字符串,需要使用单引号、双引号引用 形如 ${ __function1( "str_value" || 123)}${ __function1(key="arg_value")}${ __function1(key=\'arg_value\')}
  7. 字符串替换规则:待替换的字符串,仅包含一个函数表达式,不含其它字符,则该字符串被替换为函数返回值,如果还包含其它字符,或者包含多个函数,则该字符串替换函数表达式之前,会先转换函数返回值为字符串,然后替换这些函数表达式为转换后的函数返回值
  8. 函数参数支持python原生函数 形如 ${ __function1( set([1,2,3]) )}

解决思路

1、先解析内部函数,再解析其父函数,即从内到外解析

实现方式:查找不包含嵌套函数表达式的函数表达式,先临时替换为“临时插件函数表达式” 形如 '@plugin_func_custom_function_name@',同时以该值为字典key,存储对应临时函数表达式,然后再用替换后的字符串去查找不包含嵌套函数表达式的函数表达式,然后再替换字符串,直到找不到为止

2、解析替换后的字符串,获取“临时插件函数表达式”,然后执行调用该函数

3、函数参数类型分析

字符串参数要求用 单、双引号 引用,通过eval(参数)转换,如果转换成功则用转换后的,否则用转换前的

实现代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# import re # 插件函数样例
def base64(*args, **kwargs):
print('base64 called')
print('args:', args)
print('kwargs:', kwargs)
return 1 def read_file(*args, **kwargs):
print('fread_file called')
print('args:', args)
print('kwargs:', kwargs)
return ['a', 1] def generate_num(*args, **kwargs):
print('generate_num called')
print('args:', args)
print('kwargs:', kwargs)
return 899999 PUGIN_FUNC_MAP = {'read_file':read_file, 'base64':base64, 'generate_num':generate_num} # 存放插件函数名称和对应函数实体的映射 func_map = {} # 存放程序执行过程中,获取的临时函数名称和函数表达式的映射关系 REG_FOR_TEMP_PLUGIN_FUNC = re.compile('@(plugin_func.+?)@', re.DOTALL) # 用于查找临时插件函数名称 形如 [''@plugin_func__base64@','@plugin_func__read_file@']
REG_FOR_FUNC_NAME_OF_EXP = re.compile('\${\s*(_.+?)\(', re.DOTALL) # 用于查找函数表达式中的函数名称
REG_FOR_FUNC_NAME_AND_ARGS = re.compile('\${\s*(_.+?)\((.*?)\)\s*}', re.DOTALL) # 用于查找函数表达式中的函数定义(函数名称及其参数)
REG_FOR_STRICT_FUNC_EXP = re.compile('\${\s*_.+\(.*?\)\s*}', re.DOTALL) # 用于获取严格函数定义表达式
REG_FOR_KWARG = re.compile('^[^"\']+[^"\']+\s*=\s*.+', re.DOTALL) # 用于匹配关键词参数 def _replace_function(string):
'''替换字符串中的插件参数''' string = string.strip()
func_name_list = REG_FOR_TEMP_PLUGIN_FUNC.findall(string) # 获取函数名称列表 形如 [''@plugin_func__base64@','@plugin_func__read_file@']
if len(func_name_list) == 1 and string == '@%s@' % func_name_list[0]: # 整个字符串就是一个函数表达式,字符串代表的值的类型和函数返回值类型相同,如果函数不存在,返回None
if func_name_list[0] in func_map:
return call_plugin_func(func_map.get(func_name_list[0]))
else:
for func_name in func_name_list:
if func_name in func_map:
string = string.replace('@%s@' % func_name, str(call_plugin_func(func_map.get(func_name))))
return string def call_plugin_func(function_express):
'''
调用插件函数
''' try:
result = REG_FOR_FUNC_NAME_AND_ARGS.findall(function_express) # 查找函数表达式中的函数定义(函数名称及其参数)
if result:
plugin_func_name, plugin_func_args = result[0]
plugin_func_name = plugin_func_name.strip('_') # 去掉函数前缀标识 _ 以获取真正的函数 plugin_func_args = plugin_func_args.strip()
plugin_func_arg_list = []
if plugin_func_args:
plugin_func_arg_list = plugin_func_args.split("||") # 函数参数要求用 || 分隔 position_arg_list = [] # 存放位置参数
keyword_arg_dict = {} # 存放关键词参数 for item in plugin_func_arg_list:
item = item.strip()
if REG_FOR_KWARG.findall(item): # 关键词参数
key, value = re.split('\s*=[=|\s]*', item)
try:
value = _replace_function(value)
keyword_arg_dict[key.strip()] = eval(value)
except Exception as e:
keyword_arg_dict[key.strip()] = value
else:
try:
value = _replace_function(item)
position_arg_list.append(eval(value))
except Exception as e:
position_arg_list.append(value)
if plugin_func_name in PUGIN_FUNC_MAP:
return PUGIN_FUNC_MAP.get(plugin_func_name)(*position_arg_list, **keyword_arg_dict)
else:
return None
else: #未找到函数
print('没有找到同函数表达式( %s )匹配的函数定义' % function_express)
return None #
except Exception as e:
raise def replace_function(string):
'''替换函数''' try:
regular_obj = re.compile('\${\s*(_.+?)\(', re.DOTALL) # 获取临时函数名称
temp_func_name_list = REG_FOR_FUNC_NAME_OF_EXP.findall(string)
string_copy = string old_func_name_set = set() # 存放上一次的查找结果
while old_func_name_set != set(temp_func_name_list):
old_func_name_set = set(temp_func_name_list)
for func_name in temp_func_name_list: # 遍历查找函数对应的函数表达式
pattern = '\${\s*%s\(.*?\)\s*}' % func_name
func_express_list = re.findall(pattern, string_copy) # 获取函数表达式(因为可能存在函数嵌套,所以获取的表达式可能是错误的) if not func_express_list: # 找不到函数表达式,说明该函数名称无效,不合法
continue for func_express in func_express_list:
temp_func_express = func_express.strip().lstrip('${')
if not REG_FOR_STRICT_FUNC_EXP.findall(temp_func_express): # 表达式不包含嵌套函数,则 获取正确的函数表达式进行替换
right_func_express_list = REG_FOR_STRICT_FUNC_EXP.findall(func_express)
for right_func_express in right_func_express_list:
string_copy = string_copy.replace(right_func_express, '@plugin_func%s@' % func_name)
func_map['plugin_func%s' % func_name] = right_func_express # 建立临时函数名称和函数表达式的映射关系
temp_func_name_list = re.findall(regular_obj, string_copy) if string_copy == string: # 无变化
return string
return _replace_function(string_copy)
except Exception as e:
print('替换函数出错%s' % e)
return string # 运行测试
src_string = "some string ${ __base64( ${__read_file('filepath')} \
|| 'string_arg' || 'b==整个表达式(包括b==)是字符串参数' || '支持单双引号转义字符参数\" \
|| 'fake_key_arg1 = 我整个表达式都是字符串参数' || key_arg1='关键词字符串参数'||key_arg2=1 ||key_arg3=[1, 2, 3] \
|| key_arg4={'a':1, 'b':'字典参数'} \
) } hello" print(replace_function(src_string)) src_string = '${ __generate_num() }'
print(replace_function(src_string))

运行结果如下

Python 按规则解析字符串中的嵌套函数并实现函数调用的更多相关文章

  1. PYTHON练习题 二. 使用random中的randint函数随机生成一个1~100之间的预设整数让用户键盘输入所猜的数。

    Python 练习 标签: Python Python练习题 Python知识点 二. 使用random中的randint函数随机生成一个1~100之间的预设整数让用户键盘输入所猜的数,如果大于预设的 ...

  2. Python之数据结构:字符串中的方法

    一.过滤字符串 1.strip() (1)去掉行收尾不可见字符 a = ' wejifrow ' print a print a.strip() 结果: wejifrow wejifrow (2)st ...

  3. python,json解析字符串时ValueError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

    今天写测试工具的时候,去excel取数据,用json解析字符串为字典时报错,后经调试,发现是单引号的原因,将单引号换位双引号即可 def getExcelValue_to_dic(filepath): ...

  4. python练习题之计算字符串中所有字符得和

    第二题:计算字符串中所有数字的和1.字符串中只有小写字母和数字2.数字可能连续,也可能不连续3.连续数字要当做一个数处s='1234adg3g11's1 = "" for i in ...

  5. python第二十一课——str中的常用函数(重要)

    演示str中常用的一些函数: 1.join():将容器对象以某种特定的格式(字符串)进行拼接组合,最后以字符串的形式返回 lt=['i','love','you','very','much'] str ...

  6. 【python】实例-python实现两个字符串中最大的公共子串

    由于python中的for循环不像C++这么灵活,因此该用枚举法实现该算法: C="abcdefhe" D="cdefghe" m=0 n=len(C) E=[ ...

  7. python学习笔记 改变字符串中的某一位

    a = ' a = list(a) a[2] = ' news = ''.join(a) print news,a 注意不能使用 news = '' news.join(a) 因为news.join只 ...

  8. Python习题-输出一个字符串中最长的子字符串及其长度

    描述:有个字符串$sd1#111$svda123!!!221&eSSDSDG,包含特殊字符.数字和字母,输出最长的子字符串和他的长度#例如上面的字符串包含数字字母的字符串是svda123,长度 ...

  9. python 插入mysql数据库字符串中含有单引号或双引号报错

    出现问题场景:使用mysql数据库管理接口测试用例,新增接口用例时,传入的paras内容,有多层嵌套的时候,就会有["]双引号括住[']单引号的情况,可能在插入单双引号的数据到数据库的时候, ...

  10. 006——php字符串中的处理函数(五)

    <?php /** * 一.addslashes() 在预定义字符串前添加反斜杠 * * stripslashes() 把转义字符串前的反斜杠删除 * get_magic_quotes_gpc( ...

随机推荐

  1. win10找回Ubuntu启动项(非EasyBCD)

    最近想对装在电脑上的Ubuntu进行更新,但是之前在BIOS里改了引导系统的文件,导致找不到Ubuntu启动项,EasyBCD程序也不起作用(整块硬盘Windows分区都是GPT,改BIOS也没什么用 ...

  2. Vue——计算属性和侦听属性

    一.计算属性 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护.例如,有一个嵌套数组对象:我们想根据 author 是否已经有一些书来显示不同的 ...

  3. cmake之find_library使用问题

    附上工程源码 demo工程 PS:这个工程用于导出库 CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(demo LANGUAGES ...

  4. MySQL学习笔记-数据定义语言

    SQL-数据定义语言(DDL) 一.操作数据库 1.查询 # 查询所有数据库 show databases; # 查询当前数据库 select database(); 2.创建 create data ...

  5. vue-devtools

    今天分享个vue的开发者工具,vue2和vue3都可以使用 先来看个效果: 如何安装? 打开官网vue-devtools 选择你需要版本 安装完记得重启下浏览器,不然是没效果的

  6. Matlab打印运行进度

      在运行matlab程序的过程中,有时候需要实时地掌握程序运行的进度,尤其对于一些耗时较长的循环操作,能够及时地输出运行进度,显得非常有必要.   打印进度条的实现方式就是不断地退格.输出.   退 ...

  7. C#.NET Rsa私钥加密公钥解密

    在C#中,RSA私钥只能签名,不能加密,如果要加密,要借助BouncyCastle库. nuget 中引用 Portable.BouncyCastle. 工具类: RsaEncryptUtil usi ...

  8. HTML/CSS复习

    CSS复习 HTML语义化 有利于SEO(搜索引擎优化) 便于阅读,修改 对盲人等不方便浏览网页的人来说比较方便 盒模型 盒模型有border-box和content-box两种,默认是content ...

  9. 字节面试:MySQL自增ID用完会怎样?

    在一些中小型项目开发中,我们通常会使用自增 ID 来作为主键的生成策略,但随着时间的推移,数据库的信息也会越来越多,尤其是使用自增 ID 作为日志表的主键生成策略时,可能很快就会遇到 ID 被用完的情 ...

  10. iOS登陆界面切换到注册界面并返回的UI设计(简易向)

    功能实现 从登陆界面进入注册界面 从注册界面返回登陆界面 功能实现思路 在网上搜了搜发现各位大神用的是navigation,但个人感觉没(zhen)大(ting)必(bu)要(dong).所以在这里提 ...