如何基于任意字符串创建具有任意参数的函数

时间:2019-06-20 03:44:27

标签: python python-3.x dynamic eval function-declaration

我的最终目标是:我想创建一组真值表,其中每个真值表都对应一个任意定义的布尔表达式,该表达式最初存储为字符串(例如:“((var_1而不是var_2)或var_3”) )。该字符串可以有任意多个运算符。

如果我有一个特定的布尔表达式,这很容易实现:

def evaluator(var_1,var_2,var_3):
    return  (var_1 and not var_2) or var_3

def truth_table(f):
    values = [list(x) + [f(*x)] for x in product([False,True], repeat=f.__code__.co_argcount)]
    return pd.DataFrame(values,columns=(list(f.__code__.co_varnames) + [f.__name__]))

one_truth_table = truth_table(evaluator)

但是我想对具有任何类型的布尔表达式的任何数量的参数的函数执行此操作。我将遍历布尔表达式作为字符串以创建一系列真值表。

我整天都在挣扎。如果我可以让此代码段按我的意愿运行,那么我的问题就可以解决。

def temp_func(boolean_expression_string,variable_names_list):
    return eval(boolean_expression_string)

# i have two strings: '(var_1 and var_2) and (var_3 or not var_4) or var_etc'
# and also: 'var_1,var_2,var_3,var_4,var_etc'

temp_func('(var_1 and var_2) and (var_3 or not var_4) or var_etc', input(list(eval('var_1,var_2,var_3,var_4,var_etc'))))

运行此结果是:

NameError: name 'var_1' is not defined

如果我以一种愚蠢的方式来解决整个问题,我会把整个故事都包括在内。尽管您可能会猜测我只是想让它正常工作,但优雅并不是目前的重中之重。

编辑:变量名称不是统一定义的,并且无法根据某些顺序进行解析,因此这是处理困难的另一层

1 个答案:

答案 0 :(得分:0)

eval可以使用名称到值映射的两个字典,分别用作全局和局部名称空间,以在其中运行表达式。

新答案

请求宽恕比获得许可要容易。

这个想法是通过尝试评估表达式并捕获NameError来查找所有变量名。请注意,我们需要为变量列表生成所有变量赋值,因为python将使orand的求值短路 。例如,如果var_1 or var_2初始化为var_2,则在var_1中找不到True

def variable_names(expression):
    # list of found variables
    variables = list()
    while True:
        try:
            # generate all assignments for current variable names
            assignments = [
                {variables[i]: v for i, v in enumerate(vs)}
                for vs in itertools.product(
                    [True, False], repeat=len(variables)
                )
            ]
            # try to evaluate them all
            for assignment in assignments:
                eval(expression, None, assignment)
            # all of them work, can return
            return variables
        except NameError as e:
            # get next variable
            variables.append(
                re.match("name '(.+)' is not defined", str(e)).group(1)
            )

然后我们生成赋值词典的列表(与前面的方法完全相同),并将其提供给eval,将结果添加到字典中。然后可以从记录创建DataFrame。

def truth_table(expression):
    # get variable names
    variables = variable_names(expression)
    # make list of assignments
    assignments = [
        {variables[i]: v for i, v in enumerate(vs)}
        for vs in itertools.product([True, False], repeat=len(variables))
    ]
    # get truthy values
    values = [
        {**assignment, **{"value": eval(expression, None, assignment)}}
        for assignment in assignments
    ]
    # make dataframe from records and supply column order
    return pd.DataFrame.from_records(values, columns=variables + ["value"])

旧答案—固定变量命名。

如果所有变量都被命名为var_1var_2,...,您可以列出evaluator的值分配并将其解析为字典

def evaluator(expression, values):
    return eval(
        expression,
        None,
        {"var_{}".format(i + 1): v for i, v in enumerate(values)},
    )

并按如下所示运行它

evaluator( 
    "(var_1 and var_2) and (var_3 or not var_4)", 
    [True, False, True, False] 
)                                                                                        

返回False

然后是从表达式计算真值表的完整代码

def truth_table(expression):
    # get variable names
    varnames = set(re.findall(r"(var_\d+)", expression))
    # sort by index
    varnames = sorted(varnames, key=lambda x: int(x.split("_")[1]))
    # get truthy values
    values = [
        list(x) + [evaluator(expression, x)]
        for x in itertools.product([True, False], repeat=len(varnames))
    ]
    return pd.DataFrame(values, columns=varnames + ["T/F"])

如果变量列表中有,例如(var_1 and var_3)-您需要在调用评估程序之前重命名它们,或更改评估程序以使用字典。