Как использовать Python AST для отслеживания операций магазина? - PullRequest
0 голосов
/ 03 июля 2018

Мой отладчик использует инструментарий 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 голосов
/ 03 июля 2018

Один из вариантов - переписать цикл 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
...