Преобразование пользовательской формулы в функцию python [дубликат]
этот вопрос уже есть ответ здесь:
- создание объекта функции из строки 3 ответы
считайте, что у нас есть следующий вход
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