Python中eval’ed /动态生成函数的源代码

Source code for an eval'ed/dynamically generated function in Python

我正在寻找一种在运行时从包含源代码的字符串中创建python函数的方法,这种方法可以通过检查来获得源代码。

我目前的方法如下:

1
2
3
4
5
6
src = 'def foo(x, y):' + '
\t'
+ 'return x / y'
g = {numpy: numpy, ...}  # Modules and such required for function
l = {}
exec(src, g, l)
func = l['foo']

它工作得很好,但是函数没有与之相关的源代码/文件。这使得调试困难(请注意,在foo()中发生错误的行没有显示):

1
2
3
4
5
6
7
8
>>> foo(1, 0)
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-85-9df128c5d862> in <module>()
----> 1 myfunc(3, 0)

<string> in foo(x, y)

ZeroDivisionError: division by zero

如果我在ipython解释器中定义了一个函数,我可以使用inspect.getsource获取源代码,它将以回溯方式打印出来。对于这些类型的函数,inspect.getsourcefile返回类似于''的值,当然这不是真正的文件。在非交互环境中有没有类似的方法?


所以我通过挖掘ipython源代码部分地解决了这个问题。它利用了内置模块linecache,其中包含从文件中读取源代码和缓存结果的函数。inspecttraceback模块都使用该模块来获取函数的源。

解决方案是以与问题相同的方式创建函数,但使用具有组成的唯一文件名的compile

1
2
3
4
5
6
7
8
9
10
source = 'def foo(x, y):' + '
\t'
+ 'return x / y'

filename = '<dynamic-123456>'  # Angle brackets may be required?
code = compile(source, filename, 'exec')

g = {numpy: numpy, ...}  # Modules and such required for function
l = {}
exec(src, g, l)
func = l['foo']

linecache包含一个变量cache,它是一个将文件名映射到(size, mtime, lines, fullname)元组的字典。您只需为假文件名添加一个条目:

1
2
3
4
5
lines = [line + '
'
for line in source.splitlines()]

import linecache
linecache.cache[filename] = (len(source), None, lines, filename)

然后该函数将与inspect.getsource()、ipython的?/??语法以及ipython回溯一起工作。然而,它似乎仍然不能在内置的回溯中工作。这对我来说已经足够了,因为我几乎总是在伊普利森工作。

编辑:请参阅下面的用户2357112的注释,了解如何在内置解释器中使用回溯打印。