Преобразование пользовательской формулы в функцию python [дубликат]

этот вопрос уже есть ответ здесь:

считайте, что у нас есть следующий вход

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 дальше.

знаете ли вы о каком-либо более чистом альтернативном решении? Любая помощь или понимание будет высоко ценится!

3 ответов


можно использовать 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))"

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

для этого можно использовать так называемое форматирование строк.

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))' который вы можете выполнить в любое время с помощью


если вы измените синтаксис, используемый в формулах (другой) способ сделать это - как я уже упоминал в комментарий - было бы использовать string.Template замена.

из любопытства я решил выяснить, является ли этот другой подход жизнеспособным - и, следовательно, смог придумать лучший ответ в том смысле, что он не только проще, чем мой другое one, это также немного более гибко в том смысле, что было бы легко добавить аргументы вызываемых функций, как отмечено в комментарии ниже.

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 module (более сложным способом, чем в моем первоначальном ответе).

для случаев, когда сопоставленные функции все возвращаемые значения, которые могут быть представлены как литералы Python - как числа - и не вызываются только для побочных эффектов, которые они производят, вы можете сделать следующую модификацию, которая эффективно кэширует (aka 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