Skip to main content
 首页 » 编程设计

Python 节点转换器 : how to remove nodes

2025年05月04日7yxwkf

我正在研究 AST 操作。目前我正在尝试从输入 AST 中删除某些节点。我认为 NodeTransformer 类是适合此目的的工具。可悲的是,它的行为并不像预期的那样。

documetation说:

"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) 

这是输入文件:

l = [0, 1, 2, 3] 
 
total = 0 
 
for i in l: 
    total += i 
 
print(total) 

结果:

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 

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

有人知道为什么吗?

Python 版本:3.2.3

ast illustration

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

感谢阅读!

更新 21.8.:

我在 python 邮件列表 (python-list@python.org) 上发布了这个问题的链接。看来我覆盖太多了。没有 child 访问者,它按预期工作。

MyTransformer 的完整源代码:

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

请您参考如下方法:

不,它可以正常工作,因为你删除了自写的 generic_visit() .在ast.py的源码中可以看到, NodeTransformer是 NodeVisitor 的 child , 它有自己的 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 

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