ast.fix_missing_locations 什么时候改变树?

When does ast.fix_missing_locations change the tree?

我试图找到一个示例,其中 NodeTransformer 继承人的 visitNodeTransformer 继承人的 visit 不同 并且 在树上使用 fix_missing_locations

这是来自 documentation 的示例(由于某些原因它不起作用,即树不会改变,这取决于我们是否应用 fix_missing_locations):

from ast import *


class RewriteName(NodeTransformer):

    def visit_Name(self, node):
        return Subscript(
            value=Name(id='data', ctx=Load()),
            slice=Constant(value=node.id),
            ctx=node.ctx
        )


tree = parse('foo', mode='eval')
new_tree = fix_missing_locations(RewriteName().visit(tree))
new_uncorrected_tree = RewriteName().visit(tree)

new_tree_str = dump(new_tree, include_attributes=True)
new_uncorrected_tree_str = dump(new_uncorrected_tree, include_attributes=True)

print(new_tree_str == new_uncorrected_tree_str)  # True

以上代码问题如下:

...

tree = parse('foo', mode='eval')
new_uncorrected_tree = RewriteName().visit(tree)
new_uncorrected_tree2 = RewriteName().visit(tree)
new_tree = fix_missing_locations(new_uncorrected_tree2)

print(
    tree is new_uncorrected_tree,   # True
    tree is new_uncorrected_tree2,  # True
    tree is new_tree)               # True

treenew_uncorrected_tree1new_uncorrected_tree2new_tree 是同一个对象,因此,它们是相等的,所有与它们相关的操作(例如创建一个字符串representation) 将产生相同的结果。

如果我们最初使用不同的对象,那么结果将是两个完全不同的字符串:

...

tree1 = parse('foo', mode='eval')
tree2 = parse('foo', mode='eval')
new_tree = fix_missing_locations(RewriteName().visit(tree1))
new_uncorrected_tree = RewriteName().visit(tree2)

print(tree1 is tree2)  # False

new_tree_str = dump(new_tree, include_attributes=True)
new_uncorrected_tree_str = dump(new_uncorrected_tree, include_attributes=True)

print(dump(new_tree) == dump(new_uncorrected_tree))  # True
print(new_tree_str == new_uncorrected_tree_str)      # False

print(tree1 is tree2) - 因为tree1tree2是不同的对象,所以new_treenew_uncorrected_tree也是不同的对象。

print(dump(new_tree) == dump(new_uncorrected_tree)) - 虽然这些对象的字符串表示(很可能是值)相同。

print(new_tree_str == new_uncorrected_tree_str) - 在这里我们可以看到 RewriteName 没有填写 new_uncorrected_tree 中的属性,而 fix_missing_locations 填写了,这就是为什么我们看到了差异。

如果我们打印出树,我们可以更清楚地看到差异:

print(dump(new_tree, include_attributes=True, indent=4))
print(dump(new_uncorrected_tree, include_attributes=True, indent=4))
Expression(
    body=Subscript(
        value=Name(
            id='data',
            ctx=Load(),
            lineno=1,
            col_offset=0,
            end_lineno=1,
            end_col_offset=0),
        slice=Constant(
            value='foo',
            lineno=1,
            col_offset=0,
            end_lineno=1,
            end_col_offset=0),
        ctx=Load(),
        lineno=1,
        col_offset=0,
        end_lineno=1,
        end_col_offset=0))
Expression(
    body=Subscript(
        value=Name(id='data', ctx=Load()),
        slice=Constant(value='foo'),
        ctx=Load()))

但是我们也可以简单地通过从相同的值创建一个相同类型的对象来得到这样的结果:

class RewriteName(NodeTransformer):
    def visit_Name(self, node):
        return Name(node.id, node.ctx)

这是因为在创建节点对象时,我们没有指定它的所有属性,这正是 parse 函数中发生的情况。