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

将自定义公式转换为python函数,python,parsing,abstract-syntax-tree,dsl,Python,Parsing,Abstract Syntax Tree,Dsl,考虑到我们有以下输入 formula = "(([foo] + [bar]) - ([baz]/2) )" function_mapping = { "foo" : FooFunction, "bar" : BarFunction, "baz" : BazFunction, } 有没有python库可以让我解析公式并将其转换为 python

考虑到我们有以下输入

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

function_mapping = {
                   "foo" : FooFunction,
                   "bar" : BarFunction,
                   "baz" : BazFunction,  
                  }
有没有python库可以让我解析公式并将其转换为 python函数表示法

例如

我目前正在调查类似的事情

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))”
答案=42

如果稍微更改公式中使用的语法,另一种方法(如我在a中所述)是使用替换

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
出于好奇,我决定找出另一种方法是否可行——因此,我能够找到更好的答案,因为它不仅比我的方法简单,而且更灵活,因为在下面的注释中,可以很容易地为调用的函数添加参数

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
模块(比我在原始答案中使用的方式更复杂)

对于映射函数都返回可以表示为Python文本(如数字)的值,并且不只是因为它们产生的副作用而被调用的情况,可以进行以下修改,从而有效地缓存(aka)结果:

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))'
转换的_公式=“(15+30)-(6/2))”
评估(公式)=42

第二个示例中缺少函数的括号。其他详细信息:如果一个函数在公式中出现多次,它将被多次调用。脏标志或缓存在这里很有用。@aluriak我不知道有一种内置的方法可以自动为您记录函数。这很酷。我已经更新了我的回答时也要加上括号。谢谢你指出这一点。“这将给你字符串“((FooFunction()+BarFunction()-(BazFunction()/2))”,实际上没有……。这是完全错误的……@Mr.Me:Daniel是对的,你得到的字符串类似于[10]中的
:公式输出[10]:”(()+())-(()/2))“
。不过谢谢!在发布代码之前,我真的应该对代码进行全面测试。将函数名放入字符串将解决此问题。如果无法硬编码,则有多种方法可以获取任何函数的字符串名。有关如何执行此操作的信息,请参阅。使用
编译器.parse()可能存在一个问题
是它根据Python语法进行解析,这就是为什么它将公式中的
[foo]
转换为
列表([Name('foo'))
。公式中使用的语法是什么?@martineau True,公式结构存在问题,因为它与python列表类型冲突。我定义了公式的语法,因此可以在[18]:formula=“((foo+bar)-(baz/2))”中[19]:ast=compiler.parse(formula)in[20]:ast Out[20]中给出类似于
:Module(无、Stmt([Discard(Sub)(Add((Name('foo')、Name('bar'))))、Div((Name('baz')、Const(2‘‘‘‘‘‘‘‘)))))
您可以通过“简单”来避免使用
compiler.parse()
的语法问题正如我在下文中所展示的那样进行文本替换。也就是说,最好定义公式表达式的语法,这样它就不会与现有的Python语法冲突。例如,不使用
re
模块,可以使用替换
$
语法来做您想做的事情,这可能更容易理解站起来实施。这很好!谢谢!我一直在避免使用
eval
,因为我担心安全问题。只要我彻底验证输入,你认为这是
eval
的有效用例吗?你说的“相当好”是什么意思?
eval
如果您采取了某些预防措施,则可以-请参阅更新的答案。我想我想说的是“工作完美!”:)感谢您的更新!我将确保在使用eval之前采取了必要的预防措施。