将自定义公式转换为 python 函数

Convert custom formula to python function

假设我们有以下输入

formula = "(([foo] + [bar]) - ([baz]/2) )"

function_mapping = {
                   "foo" : FooFunction,
                   "bar" : BarFunction,
                   "baz" : BazFunction,  
                  }

是否有任何 python 库可以让我解析公式并将其转换为 python 函数表示。

例如

converted_formula = ((FooFunction() + BarFunction() - (BazFunction()/2))

我目前正在研究类似

的内容
In [11]: ast = compiler.parse(formula)

In [12]: ast
Out[12]: Module(None, Stmt([Discard(Sub((Add((List([Name('foo')]), List([Name('bar')]))), Div((List([Name('baz')]), Const(2))))))]))

然后进一步处理这棵ast树。

您知道任何更清洁的替代解决方案吗? 非常感谢任何帮助或见解!

您可以使用所谓的字符串格式化来完成此操作。

function_mapping = {
                   "foo" : FooFunction(),
                   "bar" : BarFunction(),
                   "baz" : BazFunction(),  
                  }

formula = "(({foo} + {bar}) - ({baz}/2) )".format( **function_mapping )

会给你((FooFunction() + BarFunction() - (BazFunction()/2))

的结果

但我相信函数会在加载模块时执行,所以也许更好的解决方案是

function_mapping = {
                   "foo" : "FooFunction",
                   "bar" : "BarFunction",
                   "baz" : "BazFunction",  
                  }

formula = "(({foo}() + {bar}()) - ({baz}()/2) )".format( **function_mapping )

这将为您提供字符串 '((FooFunction() + BarFunction() - (BazFunction()/2))',然后您可以随时使用 eval 函数执行该字符串。

您可以使用 re 模块通过正则表达式模式匹配和相对直接的文本替换来完成您想要的操作。

import re

alias_pattern = re.compile(r'''(?:\[(\w+)\])''')

def mapper(mat):
    func_alias = mat.group(1)
    function = function_alias_mapping.get(func_alias)
    if not function:
        raise NameError(func_alias)
    return function.__name__ + '()'

# must be defined before anything can be mapped to them
def FooFunction(): return 15
def BarFunction(): return 30
def BazFunction(): return 6

function_alias_mapping =  dict(foo=FooFunction, bar=BarFunction, baz=BazFunction)
formula = "(([foo] + [bar]) - ([baz]/2))"  # Custom formula.

converted_formula = re.sub(alias_pattern, mapper, formula)
print('converted_formula = "{}"'.format(converted_formula))

# define contexts and function in which to evalute the formula expression
global_context = dict(FooFunction=FooFunction,
                      BarFunction=BarFunction,
                      BazFunction=BazFunction)
local_context = {'__builtins__': None}

function = lambda: eval(converted_formula, global_context, local_context)
print('answer = {}'.format(function()))  # call function

输出:

converted_formula = "((FooFunction() + BarFunction()) - (BazFunction()/2))"
answer = 42

如果您稍微更改公式中使用的语法,(另一种)方法可以做到这一点 — 正如我在 — would be to use string.Template 替换中提到的那样。

出于好奇,我决定弄清楚这种其他方法是否可行 — 因此能够得出更好的答案,因为它不仅比我的 方法简单,而且更灵活一点,因为可以很容易地向正在调用的函数添加参数,如下面的评论所述。

from string import Template

def FooFunction(): return 15
def BarFunction(): return 30
def BazFunction(): return 6

formula = "(($foo + $bar) - ($baz/2))"

function_mapping = dict(foo='FooFunction()',  # note these calls could have args
                        bar='BarFunction()',
                        baz='BazFunction()')

converted_formula = Template(formula).substitute(function_mapping)
print('converted_formula = "{}"'.format(converted_formula))

# define contexts in which to evalute the expression
global_context = dict(FooFunction=FooFunction,
                      BarFunction=BarFunction,
                      BazFunction=BazFunction)
local_context = dict(__builtins__=None)
function = lambda: eval(converted_formula, global_context, local_context)

answer = function()  # call it
print('answer = {}'.format(answer))

最后一点,请注意 string.Template 支持不同类型的 高级用法 这将允许您进一步微调表达式语法 — 因为在内部它使用 re 模块(比我原来的答案更复杂)。

对于映射函数的所有 return 值都可以表示为 Python 文字的情况——比如数字——并且不只是因为它们产生的副作用而被调用,你可以进行以下有效缓存(又名 memoize)结果的修改:

function_cache = dict(foo=FooFunction(),  # calls and caches function results
                      bar=BarFunction(),
                      baz=BazFunction())

def evaluate(formula):
    print('formula = {!r}'.format(formula))
    converted_formula = Template(formula).substitute(function_cache)
    print('converted_formula = "{}"'.format(converted_formula))
    return eval(converted_formula, global_context, local_context)

print('evaluate(formula) = {}'.format(evaluate(formula)))

输出:

formula = '(($foo + $bar) - ($baz/2))'
converted_formula = "((15 + 30) - (6/2))"
evaluate(formula) = 42