在用户输入字符串上使用 numba.jit

Using numba.jit on a user input string

我正在尝试编写一个程序,将用户输入函数作为字符串接收,并使用该函数进行大量计算。这些计算是使用 numba.jit 完成的,如果我对我的函数进行硬编码,代码就可以工作,但是我无法弄清楚如何解析字符串,以便我可以使用 nopython= 将它变成一个 jitted 函数没错。

例如,我的代码运行函数

@jit(nopython=True)
def f(x):
    return x*x

但我想取而代之的是获取用户输入字符串 'x*x' 并创建相同的函数。我试过使用 SymPy,但我无法让它与 jit 一起很好地发挥作用。有什么想法吗?

假设您确实想要将任意 Python 代码作为用户输入并 运行 它—您通常真的、真的不想这样做,但让我们假设您有一个很好的理由…

但首先:您提到尝试使用 SymPy 来做到这一点。如果您实际上是在尝试使用 SymPy 表达式创建函数,例如,通过使用 sympifylambdify——那应该可行,如果不行,您需要向我们展示您的代码,如果你需要帮助调试它。

但让我们停止拖延,看看如何做您要求的事情,即使这很可能不是您真正想要的。


记住装饰器只是函数,它接受一个函数和 return 另一个函数,您可以正常调用它们。因此,您所要做的就是将任意 Python 代码转换为函数,然后将其传递给装饰器。


如果任意 Python 代码只是一个表达式,您可以将其包装在 lambda 表达式中,eval 结果,并且您有一个应用该表达式的函数表达式:

lambdastr = f'lambda x: {user_string}'
lambdafunc = eval(lambdastr)
numbafunc = numba.jit(nopython=True)(lambdafunc)

或者,如果您愿意:

numbafunc = numba.jit(nopython=True)(eval(f'lambda x: {user_string}'))

如果您认为 "But wait, eval is dangerous" — 嗯,是的,eval 很危险,因为它将任意用户字符串计算为代码,而这正是您想要做的。没有非危险的方法可以做到这一点。

因此,如果您的用户将字符串 x * x 传递给您,您现在就有了一个对其输入求平方的函数,如果用户将字符串 __import__('os').system('rm -rf /') 传递给您,您现在已经有一个功能会尝试擦除整个硬盘驱动器。


如果你想获取一个语句,你可以通过将它包装在 def 中并调用 exec:

来有效地做同样的事情
defstr = f'def __(x): {user_string}'
deffunc = exec(defstr)
numbafunc = numba.jit(nopython=True)(deffunc)

如果那个任意 Python 代码可以是一个语句块,由于您需要处理缩进,所以它会稍微复杂一些,但这并不难:

user_lines = '\n'.join(' '+line for line in user_string.splitlines())
defstr = f'def __(x):\n{user_lines}'
deffunc = exec(defstr)
numbafunc = numba.jit(nopython=True)(deffunc)