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

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

需求

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))

运行结果如下
img

posted @ 2021-05-05 20:48  授客  阅读(864)  评论(0编辑  收藏  举报