我有以下功能
def foo():
for _ in range(1):
print("hello")
现在我想添加另一个print语句,在每次循环迭代后打印“Loop iterated”,为此我定义了一个新函数,将foo转换为ast树,插入相应的print节点,然后将ast树编译成一个可执行函数:
def modify(func):
def wrapper():
source_code = inspect.getsource(func)
ast_tree = ast.parse(source_code)
# insert new node into ast tree
for node in ast.walk(ast_tree):
if isinstance(node, ast.For):
node.body += ast.parse("print('Loop iterated')").body
# get the compiled function
new_func = compile(ast_tree, '<ast>', 'exec')
namespace = {}
exec(new_func, globals(), namespace)
new_func = namespace[func.__name__]
return new_func()
return wrapper
在使用以下项时,此功能可以正常工作:
foo = modify(foo)
foo()
但是,如果我决定使用modify
作为装饰器:
@modify
def foo():
for _ in range(1):
print("hello")
foo()
出现以下错误:
Traceback (most recent call last):
File "c:\Users\noinn\Documents\decorator_test\test.py", line 34, in <module>
foo()
File "c:\Users\noinn\Documents\decorator_test\test.py", line 25, in wrapper
return new_func()
File "c:\Users\noinn\Documents\decorator_test\test.py", line 11, in wrapper
source_code = inspect.getsource(func)
File "C:\Users\noinn\AppData\Local\Programs\Python\Python39\lib\inspect.py", line 1024, in getsource
lines, lnum = getsourcelines(object)
File "C:\Users\noinn\AppData\Local\Programs\Python\Python39\lib\inspect.py", line 1006, in getsourcelines
lines, lnum = findsource(object)
File "C:\Users\noinn\AppData\Local\Programs\Python\Python39\lib\inspect.py", line 835, in findsource
raise OSError('could not get source code')
OSError: could not get source code
有人知道为什么会出现这个错误吗?请注意,如果我返回原始函数,并且错误只在调用new_func()时出现,则不会出现这种情况。
------------------------解决方案---------
只需使用以下命令将装饰器从装饰器本身的函数中移除:
ast_tree.body[0].decorator_list = []
1条答案
按热度按时间dldeef671#
经过一些实验,我发现我最初的假设是不正确的:
inspect.getsource
足够聪明,即使在模块globals中还没有设置函数名,也可以检索源代码(令人惊讶)。所发生的事情是,源代码与 decorator 调用沿着被检索,当函数被调用时,decorator再次运行-因此,它在
inspect.getsource
中获得了一些可重入性,在这些点上它失败了。解决方案如下:我只是在检索到的源代码中剥离模块级的装饰器,然后将其提供给
ast.parse
我还重新安排了装饰器,因为它将重新读取源代码,并在每次调用装饰函数时重新解析AST--按照本例的方式,根本不需要内部
wrapper
函数。如果您碰巧需要参数化装饰器,并且需要内部wrapper
,所有函数重写部分,直到创建new_func
,应该在 Package 之外,这样它们就只运行一次初步答复
在此进行推理和更简单的解决方法:
它不能获得
<module>.foo
函数的源代码,原因很简单,即名称foo
只会在函数定义(包括所有装饰器)执行之后定义和绑定。在装饰器代码运行时,名称
foo
并不作为变量存在于模块中,尽管此时它被设置为func
中的__name__
属性,但inspect.getsource
对此无能为力。换句话说,如果使用的是
inspect.getsource
,装饰器语法是完全不受限制的,因为getsource
还不能获取任何源代码。好消息是解决方法很简单:我们必须放弃
@
装饰器语法,并“以旧的方式”应用装饰器(就像Python 2.3之前那样):一个像往常一样声明函数,并将其名称重新分配给手动调用装饰器的返回值--这与你的工作示例中所写的相同: