如何在Python中获取完整的AST?

6
我非常喜欢_ast模块提供的选项,它非常强大。有没有一种方法可以从中获取完整的AST呢?
例如,如果我获取以下代码的AST:
import os
os.listdir(".")

通过使用:
ast = compile(source_string,"<string>","exec",_ast.PyCF_ONLY_AST)
ast对象的主体将有两个元素,一个import对象和一个expr对象。然而,我想更进一步,获取importlistdir的AST,换句话说,我想使_ast尽可能下降到最低级别。
我认为这种事情应该是可能的。问题是如何
编辑:我所说的最低级别不是访问“可见”的内容。我也想获取listdir的实现的AST:像执行它的stat和其他函数调用一样。

请记住,除非您的Python代码遵循一些约定,否则您必须实际执行代码才能找出在代码的某个点使用了哪个模块/函数。 - Torsten Marek
那我该如何开始做这件事呢? - Geo
一般而言是不行的。你可以这样做:import os; import random; if random.random() > .5: os.listdir = lambda *args: None; os.listdir("."); 但是,要确定执行了哪些代码会有些棘手。即使是方法调用也很困难,因为你必须静态重建类层次结构和MRO。 - Torsten Marek
2个回答

8

这种方法可以获取整个树形结构,一直到底部,但是它确实是以树的形式保存的,因此在每个级别上,要获取子节点,必须明确访问所需的属性。例如(我将compile结果命名为cf而不是ast,因为那会隐藏标准库ast模块--我假设您只有2.5而不是2.6,这就是为什么您要使用低级别的_ast模块?)...:

>>> cf.body[0].names[0].name
'os'

这段代码告诉你,import语句正在导入名称为os的模块(仅此一个,因为.body[0].names字段长度为1,这个字段是import的一部分)。

在Python 2.6的ast模块中,你还可以获得更多的帮助来更轻松地遍历这棵树(例如通过Visitor设计模式)。但无论是在2.5版(使用_ast)还是2.6版(使用ast),整个树都存在,并且以完全相同的方式表示。

为了方便地访问树中的所有节点,在2.6中,使用模块ast(没有前导下划线),并适当地子类化ast.NodeVisitor(或者等效地使用ast.iter_child_nodes进行递归和ast.iter_fields进行必要的操作)。当然,如果由于某种原因被困在2.5中,则可以在_ast之上纯Python实现这些辅助程序。


快速问题:cf代表什么?我在文档中看到了这个缩写,你也使用了它,但我不知道它代表什么。编译文件?代码格式化?代码文件?代码字段?编译字段?编译格式? - ArtOfWarfare
1
@ArtOfWarfare,我相信我心中所想的“compiled form”的缩写是无害的。 - Alex Martelli
谢谢,这很有道理。我不喜欢在我的代码中使用不常见的缩写 - 我更喜欢变量名简短且描述性强。所以我会将其称为compiledForm而不是cf - ArtOfWarfare

5
py> ast._fields
('body',)
py> ast.body
[<_ast.Import object at 0xb7978e8c>, <_ast.Expr object at 0xb7978f0c>]
py> ast.body[1]
<_ast.Expr object at 0xb7978f0c>
py> ast.body[1]._fields
('value',)
py> ast.body[1].value
<_ast.Call object at 0xb7978f2c>
py> ast.body[1].value._fields
('func', 'args', 'keywords', 'starargs', 'kwargs')
py> ast.body[1].value.args
[<_ast.Str object at 0xb7978fac>]
py> ast.body[1].value.args[0]
<_ast.Str object at 0xb7978fac>
py> ast.body[1].value.args[0]._fields
('s',)
py> ast.body[1].value.args[0].s
'.'

HTH


我知道如何获得它。问题是,我怎么才能获取listdir的AST?不是函数参数,而是底层实现。 - Geo
listdir 没有 AST - 它是用 C 实现的。 - Martin v. Löwis
那么我该如何获取所有可以获取的内容呢?我该如何让_ast递归进入每个Python实现的函数中? - Geo
2
@Geo,如我在我的答案中所提到的:升级到2.6版本,使用没有前导下划线的ast模块,并适当地子类化ast.NodeVisitor(或等效地递归地使用ast.iter_child_nodes和根据需要使用ast.iter_fields)。 - Alex Martelli
你也不能在Python实现的函数中进行“递归”。首先,如果你有“foo.bar()”,你甚至不知道调用了哪个bar,因为由于延迟绑定,你实际上必须运行该代码。即使对于模块级函数:仅编译对它们的调用时,实际编译的函数根本不被考虑。它可能不存在,即使它确实存在,你也只能获得函数的字节码。如果你想要函数的AST,则需要找到模块源代码并自己编译它。从你的代码到模块的“自动”遍历是不可能的。 - Martin v. Löwis
这个问题和答案已经很老了。然而,这个小例子真的帮助我理解了ast对象的树形结构,我想说谢谢你。谢谢! - aezell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接