Python 通过函数装饰器改造 ast
Python transforming ast through function decorators
我有一个想法来转换使用类似于下面的装饰器标记的所有给定函数,
@transform_ast
def foo(x):
return x
在transform_ast
中,我获取源代码,提取ast,对其进行转换,然后再次从中创建代码对象和函数类型。它看起来像下面,
import ast
import inspect
import types
class Rewrite(ast.NodeTransformer):
pass
def transform_ast(f):
source = inspect.getsource(f)
source = '\n'.join(source.splitlines()[1:]) # remove the decorator first line.
print(source)
old_code_obj = f.__code__
old_ast = ast.parse(source)
new_ast = Rewrite().visit(old_ast)
new_code_obj = compile(new_ast, old_code_obj.co_filename, 'exec')
new_f = types.FunctionType(new_code_obj, {})
return new_f
@transform_ast
def foo(x):
return x
然而,当我随后调用 foo(x) 时,它似乎无法正常工作。
出于所有实际目的,我们可以假设我的转换只是将 return x
重写为 return x+1
。理想情况下,我希望一切都能正常工作,包括能够使用调试器进入函数...
调用foo(10)
,会出现以下错误,
TypeError: module() takes no arguments (1 given)
我做错了什么吗?
new_code_obj = compile(new_ast, old_code_obj.co_filename, 'exec')
使用exec
模式编译的代码总是被视为模块级代码,当然,它可以包含函数或 class 定义,或任何其他有效的 Python).
要验证这一点,您可以访问代码对象的 co_name
属性以获取定义此代码对象的 名称.
>>> new_code_obj.co_name
<module>
即new_code_obj
是一个模块对应的代码对象。但是函数对应的代码对象在哪里foo
。我们如何访问它?
它可以从代码对象的 co_consts
属性访问,它是字节码中使用的 常量元组
>>> new_code_obj.co_consts
(<code object foo at 0x031C3DE0, file "c:/Users/test.py", line 1>, 'foo', None)
>>> new_code_obj.co_consts[0]
<code object foo at 0x031C3DE0, file "c:/Users/test.py", line 1>
要验证此代码对象来自函数 foo
,您可以再次使用 co_name
属性。
>>> new_code_obj.co_consts[0].co_name
foo
因此,在创建新的 FunctionType
时,您应该使用与函数 foo
相对应的代码对象,而不是 module
代码对象。
所以改变
new_f = types.FunctionType(new_code_obj, {})
到
new_f = types.FunctionType(new_code_obj.co_consts[0], f.__globals__)
# Here `f` is the function object passed to the `transform_ast`
将解决问题。
我有一个想法来转换使用类似于下面的装饰器标记的所有给定函数,
@transform_ast
def foo(x):
return x
在transform_ast
中,我获取源代码,提取ast,对其进行转换,然后再次从中创建代码对象和函数类型。它看起来像下面,
import ast
import inspect
import types
class Rewrite(ast.NodeTransformer):
pass
def transform_ast(f):
source = inspect.getsource(f)
source = '\n'.join(source.splitlines()[1:]) # remove the decorator first line.
print(source)
old_code_obj = f.__code__
old_ast = ast.parse(source)
new_ast = Rewrite().visit(old_ast)
new_code_obj = compile(new_ast, old_code_obj.co_filename, 'exec')
new_f = types.FunctionType(new_code_obj, {})
return new_f
@transform_ast
def foo(x):
return x
然而,当我随后调用 foo(x) 时,它似乎无法正常工作。
出于所有实际目的,我们可以假设我的转换只是将 return x
重写为 return x+1
。理想情况下,我希望一切都能正常工作,包括能够使用调试器进入函数...
调用foo(10)
,会出现以下错误,
TypeError: module() takes no arguments (1 given)
我做错了什么吗?
new_code_obj = compile(new_ast, old_code_obj.co_filename, 'exec')
使用exec
模式编译的代码总是被视为模块级代码,当然,它可以包含函数或 class 定义,或任何其他有效的 Python).
要验证这一点,您可以访问代码对象的 co_name
属性以获取定义此代码对象的 名称.
>>> new_code_obj.co_name
<module>
即new_code_obj
是一个模块对应的代码对象。但是函数对应的代码对象在哪里foo
。我们如何访问它?
它可以从代码对象的 co_consts
属性访问,它是字节码中使用的 常量元组
>>> new_code_obj.co_consts
(<code object foo at 0x031C3DE0, file "c:/Users/test.py", line 1>, 'foo', None)
>>> new_code_obj.co_consts[0]
<code object foo at 0x031C3DE0, file "c:/Users/test.py", line 1>
要验证此代码对象来自函数 foo
,您可以再次使用 co_name
属性。
>>> new_code_obj.co_consts[0].co_name
foo
因此,在创建新的 FunctionType
时,您应该使用与函数 foo
相对应的代码对象,而不是 module
代码对象。
所以改变
new_f = types.FunctionType(new_code_obj, {})
到
new_f = types.FunctionType(new_code_obj.co_consts[0], f.__globals__)
# Here `f` is the function object passed to the `transform_ast`
将解决问题。