Python:来自字符串评估的快速函数?
Python: fast functions from string evaluations?
我可能会完全错误地解决这个问题,但我很好奇这是否可以在 Python 中完成。
我正在尝试构建一个接受字符串的函数和 returns 一个基于该字符串的函数。例如,给定 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)
returns
0.16757759497
0.0262638996569
任何帮助,包括解释为什么不能这样做或为什么这是一种愚蠢的方法,我们将不胜感激。
提前致谢。
您可以预编译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
执行此操作的安全方法很痛苦。您将解析表达式,然后发出一个 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()
然后对树进行清理...但是清理树的难度不容小觑...
You are given a string (e.g. from a web input field or something) and need to be able to make fast calls to that string.
请记住,如果您未能正确清理树,将很容易成为针对您的 Web 服务器的攻击媒介。
另一种方法可能会更快,具体取决于您需要计算表达式的次数 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
接受一个字符串和 returns 一个对 sympy
有意义的表达式。请注意,这仍然存在风险,因为据我所知,sympify
使用 eval
。另一方面,使用 sympy
确实可以让您访问大量符号代数处理工具。
编辑:差点忘了:此代码提供了一个函数,该函数使用 math
库中的 exp
版本。使用 np
中的那个很容易;请参阅 lambdify
文档。
我可能会完全错误地解决这个问题,但我很好奇这是否可以在 Python 中完成。
我正在尝试构建一个接受字符串的函数和 returns 一个基于该字符串的函数。例如,给定 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)
returns
0.16757759497
0.0262638996569
任何帮助,包括解释为什么不能这样做或为什么这是一种愚蠢的方法,我们将不胜感激。
提前致谢。
您可以预编译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
执行此操作的安全方法很痛苦。您将解析表达式,然后发出一个 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()
然后对树进行清理...但是清理树的难度不容小觑...
You are given a string (e.g. from a web input field or something) and need to be able to make fast calls to that string.
请记住,如果您未能正确清理树,将很容易成为针对您的 Web 服务器的攻击媒介。
另一种方法可能会更快,具体取决于您需要计算表达式的次数 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
接受一个字符串和 returns 一个对 sympy
有意义的表达式。请注意,这仍然存在风险,因为据我所知,sympify
使用 eval
。另一方面,使用 sympy
确实可以让您访问大量符号代数处理工具。
编辑:差点忘了:此代码提供了一个函数,该函数使用 math
库中的 exp
版本。使用 np
中的那个很容易;请参阅 lambdify
文档。