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`

将解决问题。

附加参考:Exploring Python Code Objects