如何使用Python AST来跟踪存储操作?

时间:2018-07-03 07:21:40

标签: python python-3.x abstract-syntax-tree interpreter

My debugger使用AST工具来获取代码执行的所有逻辑步骤(包括表达式评估中的步骤)的通知。

我无法确定第一步-for循环即将为循环变量分配新值的那一刻。

For节点内部,循环变量(或更复杂的事物)由target属性内的表达式表示。该表达式的ctx属性设置为ast.Store()。我不知道如何跟踪该节点的使用情况。

作为一种特殊情况,我可以用索引locals()替换简单的循环变量:

for locals()["i"] in range(10):
    print(i)

这将给我ctx=ast.Load()节点内的一个ctx=ast.Store()节点,我知道如何跟踪它。不幸的是,这无法扩展到更复杂的目标。

解释器如何使用这些ctx=ast.Store()表达式?我可以以某种方式直接对它们进行检测以在解释器执行存储操作时得到通知吗?

1 个答案:

答案 0 :(得分:3)

一个选择是重写for循环,以便该分配以一个临时变量为目标,并将您的跟踪代码插入循环主体中。例如,像这样的循环:

for foo.x in range(3):
    print(foo.x)

可以重写为:

for _temp in range(3):
    print('loop variable will be set to', _temp)
    foo.x = _temp
    print(foo.x)

为此,我们实现了NodeTransformer

class ForLoopRewriter(ast.NodeTransformer):
    def __init__(self, nodes_to_insert):
        super().__init__()
        self.nodes_to_insert = nodes_to_insert

    def visit_For(self, node):
        # redirect the assignment to a usually invalid variable name so it
        # doesn't clash with other variables in the code
        target = ast.Name('@loop_var', ast.Store())

        # insert the new nodes
        loop_body = self.nodes_to_insert.copy()

        # then reassign the loop variable to the actual target
        reassign = ast.Assign([node.target], ast.Name('@loop_var', ast.Load()))
        loop_body.append(reassign)

        # visit all the ast nodes in the loop body
        for n in node.body:
            loop_body.append(self.visit(n))

        # make a new For node and return it
        new_node = ast.For(target, node.iter, loop_body, node.orelse)
        ast.fix_missing_locations(new_node)
        return new_node

可以这样使用:

code = '''
class Foo:
    @property
    def x(self):
        pass

    @x.setter
    def x(self, x):
        print('Setting x')

foo = Foo()
itr = (print('yielding', x) for x in range(1))

for foo.x in itr:
    pass
'''

tree = ast.parse(code)
tracing_code = ast.parse('print("Your tracing code")').body
tree = ForLoopRewriter(tracing_code).visit(tree)
codeobj = compile(tree, 'foo.py', 'exec')
exec(codeobj)

# output:
# yielding 0
# Your tracing code
# Setting x