【问题标题】:Python transforming ast through function decoratorsPython 通过函数装饰器转换 ast
【发布时间】:2019-12-20 03:58:05
【问题描述】:

我有一个想法来转换所有使用类似于下面的装饰器标记的给定函数,

@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() 不接受任何参数(给定 1 个)

我做错了什么吗?

【问题讨论】:

    标签: python abstract-syntax-tree python-decorators


    【解决方案1】:
    new_code_obj = compile(new_ast, old_code_obj.co_filename, 'exec')
    

    使用exec 模式编译的代码始终被视为模块级代码,当然,它可以包含函数或类定义,或任何其他有效的 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

    【讨论】:

    • 这个不行,比如def foo(): return range(10)试试装饰器
    • 现在好多了;),+1。最后一件事,op 在使用多个装饰器时应该小心,因为它现在强制用户在最顶部指定 @transform_ast
    猜你喜欢
    • 2015-08-03
    • 1970-01-01
    • 1970-01-01
    • 2021-10-01
    • 2012-11-01
    • 2021-12-04
    • 2014-07-21
    • 2021-04-21
    • 2016-07-25
    相关资源
    最近更新 更多