Python NodeTransformer:如何删除节点?

Tob*_*asM 5 python abstract-syntax-tree python-3.2

我正在尝试 AST 操作。目前我正在尝试从输入 AST 中删除某些节点。我认为 NodeTransformer 类是实现此目的的合适工具。遗憾的是,它的表现并不如预期。

文档说

“NodeTransformer 将遍历 AST,并使用访问者方法的返回值来替换或删除旧节点。如果访问者方法的返回值为 None,则该节点将从其位置中删除,否则将替换为返回值。”

现在看看我的程序:

import _ast
import ast
import sys

#ast transformer
class MyTransformer(ast.NodeTransformer):

    def iterate_children(self, node):
        """
        helper
        """
        children = ast.iter_child_nodes(node)
        for c in children:
            self.visit(c)

    def generic_visit(self, node):
        """
        default behaviour
        """
        print("visiting: "+node.__class__.__name__)
        self.iterate_children(node)
        return node

    def visit_For(self, node):
        """
        For nodes: replace with nothing
        """
        print("removing a For node")
        return None



#read source program
filename = sys.argv[1]
with open (filename, "r") as myfile:
    source = str(myfile.read())

#compile source to ast
m = compile(source, "<string>", "exec", _ast.PyCF_ONLY_AST)

#do ast manipulation
t = MyTransformer()
t.visit(m)

# fix locations
m = ast.fix_missing_locations(m)

#visualize the resulting ast
#p = AstPrinter()
#p.fromAst(m)

#execute the transformed program
print("computing...")
codeobj = compile(m, '<string>', 'exec')
exec(codeobj)
Run Code Online (Sandbox Code Playgroud)

这是输入文件:

l = [0, 1, 2, 3]

total = 0

for i in l:
    total += i

print(total)
Run Code Online (Sandbox Code Playgroud)

结果:

visiting: Module
visiting: Assign
visiting: Name
visiting: Store
visiting: List
visiting: Num
visiting: Num
visiting: Num
visiting: Num
visiting: Load
visiting: Assign
visiting: Name
visiting: Store
visiting: Num
removing a For node
visiting: Expr
visiting: Call
visiting: Name
visiting: Load
visiting: Name
visiting: Load
computing...
6
Run Code Online (Sandbox Code Playgroud)

我预期是“0”,因为循环已被删除。但有一个“6”(=0+1+2+3)。

有人知道为什么吗?

Python版本:3.2.3

ast 插图

( ) 中的数字表示输入程序中的行号。这里不提供图像绘制的代码;请忽略“根”节点。如您所见,For 循环仍然存在。

谢谢阅读!

更新21.8:

我在 python 邮件列表 (python-list@python.org) 上发布了这个问题的链接。看来我重写太多了。如果没有儿童访客,它会按预期工作。

MyTransformer的完整源代码:

class MyTransformer(ast.NodeTransformer):
    def visit_For(self, node):
        """
        For nodes: replace with nothing
        """
        print("removing a For node")
        return None
Run Code Online (Sandbox Code Playgroud)

VeL*_*err 2

不,它可以正常工作,因为您删除了自写的generic_visit(). 正如您在 的源代码中看到的ast.pyNodeTransformer是 的子级NodeVisitor,它有自己的generic_visit()方法。此方法执行ast节点的更新,如果您覆盖此方法,您应该知道您在做什么。覆盖将改变 的所有逻辑NodeTransformer

如果您仍然需要重写generic_visit()(例如,visiting: <AST object>在访问节点时打印消息),则必须generic_visit(). 所以,你的方法将是下一个:

def generic_visit(self, node):
        """
        printing visit messages
        """
        super().generic_visit(node)
        print("visiting: "+node.__class__.__name__)
        self.iterate_children(node)
        return node
Run Code Online (Sandbox Code Playgroud)

在这种情况下,它iterate_children()不会影响结果,但也必须删除。它迫使访问者跑过每个节点的子节点。但generic_visit()已经运行在所有节点上。因此,iterate_children()您多次访问某些节点。这会浪费计算时间,并且在更复杂的情况下可能会出错。