我可能会解决这个问题,但我很好奇是否可以在Python中完成。
我正在尝试构建一个接受字符串并返回基于该字符串的函数的函数。例如,给定b*exp(a*x)
和输入列表['a','b','c']
有没有办法在Python中动态创建此函数?
def f_fast(a, b, x):
return b*np.exp(a*x)
我可以看到如何使用eval
创建慢速版本:
np_funcs = {'exp':np.exp, 'sin':np.sin, 'cos':np.cos}
def make_func(s, vars):
def f(*x):
d = {e:x[i] for i, e in enumerate(vars)}
values = dict(d.items() + np_funcs.items())
return eval(s, {"__builtins__": None}, values)
return f
s = 'b*exp(a*x)'
f = make_func(s, ['a', 'b', 'x'])
但是这个函数会对每个调用进行字符串评估。我想知道是否有一种方法只在创建时将字符串转换为函数,然后后续调用将很快。
目前这种实施非常缓慢:
x = np.linspace(0,1,10)
print timeit.timeit('f(1,2,x)', "from __main__ import f, x, f_fast", number=10000)
print timeit.timeit('f_fast(1,2,x)', "from __main__ import f, x, f_fast", number=10000)
返回
0.16757759497
0.0262638996569
任何帮助,包括解释为什么不能这样做或为什么这是一个愚蠢的方法,将不胜感激。
提前谢谢。
答案 0 :(得分:2)
这样做的安全方法是痛苦。您将解析表达式,然后发出一个AST,您可以将其传递给compile()
。创建AST如下所示:
import ast
expr = ast.Expression(
lineno=1,
body=ast.Lambda(
args=ast.arguments(
args=[
ast.arg(arg='a'),
ast.arg(arg='b'),
ast.arg(arg='x'),
],
kwonlyargs=[],
kw_defaults=[],
defaults=[],
),
body=ast.BinOp(
left=ast.Name(id='b', ctx=ast.Load()),
op=ast.Mult(),
right=ast.Call(
func=ast.Attribute(
value=ast.Name(id='np', ctx=ast.Load()),
attr='exp',
ctx=ast.Load(),
),
args=[ast.BinOp(
left=ast.Name(id='a', ctx=ast.Load()),
op=ast.Mult(),
right=ast.Name(id='x', ctx=ast.Load()),
)],
keywords=[],
),
),
),
)
ast.fix_missing_locations(expr)
然后,您可以将其转换为优化功能并调用它:
code = compile(expr, 'myinput', 'eval', optimize=1)
func = eval(code, {'np': np})
print(func(1, 2, 3))
创建AST的任务很难。您可以自己创建,如上所述,也可以将标志ast.PyCF_ONLY_AST
传递给compile()
,然后对树进行消毒......但消毒树的难度不容小觑......
您将获得一个字符串(例如来自Web输入字段或其他内容),并且需要能够快速调用该字符串。
请记住,如果您未能正确清理树,则会导致针对您的Web服务器的攻击向量非常容易。
答案 1 :(得分:1)
您可以预编译eval表达式中使用的字符串。请注意下面的代码只是为了说明这个概念,具体的例子可以通过评估lambda来实现(如评论中所示)。
fc = compile('b*np.exp(a*x)', '<string>', 'eval')
def f_faster(a, b, x):
return eval(fc)
然后:
x = np.linspace(0, 1, 10)
print(timeit.timeit('f_faster(1,2,x)', "from __main__ import f_faster, x, fc", number=10000))
print(timeit.timeit('f_fast(1,2,x)', "from __main__ import f, x, f_fast", number=10000))
给出:
0.0241389274597
0.0203421115875
答案 2 :(得分:1)
另一种方法可能会更快,具体取决于您需要评估表达式 vs 将其从字符串转换为函数的次数。
>>> from sympy import *
>>> from sympy.utilities.lambdify import lambdify
>>> f = lambdify((x,a,b), sympify('b*exp(a*x)'))
>>> f(1,1,1)
2.7182818284590451
sympify
接受一个字符串并返回一个对sympy
有意义的表达式。请注意,这仍然存在风险,因为AFAIK sympify
使用eval
。另一方面,使用sympy
可以访问一系列符号代数处理工具。
编辑:几乎忘了:此代码提供了一个使用exp
库中math
版本的函数。使用np
中的一个很容易;请参阅lambdify
doc。