在本教程中,我们将学习如何使用 AST 来理解代码。
什么是 ast
模块?
AST 代表抽象语法树,,它是 Python 编程语言的有力工具。它允许我们与 Python 代码本身进行交互,并可以对其进行修改。
你有没有想过 Python 代码是如何运行的?背后有魔法吗?
对于不知道的人,Python 解释器负责运行 Python 代码。它遵循预先编写的指令,将 Python 代码翻译成机器可以运行的指令。
下面是将 Python 代码转换为机器代码的过程。
- 当我们运行代码时,代码被解析成称为标记的更小的块。这些令牌是由应该区别对待的预定义指令创建的。例如,关键字或是不同于数值的关键字,如
- 令牌存储在转换后的列表中,以构建抽象语法树。AST 是基于 Python 语言语法链接在一起的两个或多个节点的集合。
- 编译器可以从 AST 产生称为二进制代码的低级指令。这段代码非常通用,因此计算机可以轻松运行。
- 当解释器得到类似字节码的指令时,解释器现在可以运行代码了。字节码负责在操作系统中调用函数,最终将与 CPU 和内存交互运行程序。
上面的描述是解释器如何使用 AST 运行 Python 代码的粗略草图。
代码编译模式
有三种模式可以编译代码。它们如下所示。
- exec - 该模式用于执行正常的 Python 代码。
- eval - 该模式用于评估 Python 的表达式,评估后会返回结果。
- single - 该模式作为 Python shell 工作,一次执行一条语句。
执行 Python 代码
使用 ast
模块,我们可以运行 Python 代码。让我们理解下面的例子。
示例-
import ast
code = ast.parse("print('Hello Learner ! Welcome to JavaTpoint')")
print(code)
exec(compile(code, filename="", mode="exec"))
输出:
<_ast.module object="" at="">
Hello Learner! Welcome to JavaTpoint
评估 Python 表达式
ast
模块允许我们评估 Python 表达式,并从表达式中返回结果。让我们理解下面的例子。
示例-
import ast
expression = '6 + 8'
code = ast.parse(expression, mode='eval')
print(eval(compile(code, '', mode='eval')))
print(ast.dump(code))
输出:
14
Expression(body=BinOp(left=Constant(value=6, kind=None), op=Add(), right=Constant(value=8, kind=None)))
创建多行 ASTs
在前面的例子中,我们已经看到了单行 AST 以及如何转储它们。现在,我们将学习如何创建多行 AST。首先,让我们理解下面的例子。
示例-
import ast
tree_ast = ast.parse('''
subjects = ['computer science', 'alorithm']
name = 'Ricky'
for sub in subjects:
print('{} learn {}'.format(name, subjects))
''')
print(ast.dump(tree_ast))
输出:
Module(body=[Assign(targets=[Name(id='subjects', ctx=Store())], value=List(elts=[Constant(value='computer science', kind=None), Constant(value='alorithm', kind=None)], ctx=Load()), type_comment=None), Assign(targets=[Name(id='name', ctx=Store())], value=Constant(value='Ricky', kind=None), type_comment=None), For(target=Name(id='fruit', ctx=Store()), iter=Name(id='fruits', ctx=Load()), body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Call(func=Attribute(value=Constant(value='{} learn {}', kind=None), attr='format', ctx=Load()), args=[Name(id='name', ctx=Load()), Name(id='subjects', ctx=Load())], keywords=[])], keywords=[]))], orelse=[], type_comment=None)], type_ignores=[])
节点转换器和节点访问者
节点变压器类用于根据我们的要求采取不同的类型和修改。ast 模块还提供了 NodeVisitor 类,帮助我们在每次遍历树的时候调用访问函数。为了更好地控制节点,我们来理解下面的例子。
示例- 1
import ast
class Visitor(ast.NodeVisitor):
def visit_Str(self, node):
print('String Node: "' + node.s + '"')
class MyTransformer(ast.NodeTransformer):
def visit_Str(self, node):
return ast.Str('str: ' + node.s)
parsed = ast.parse("print('Hello World')")
MyTransformer().visit(parsed)
Visitor().visit(parsed)
输出:
Welcome to the Javatpoint
解释-
在上面的代码中,我们已经导入了解析代码的 ast 模块。然后我们定义了继承了节点访问者类的访问者类。每次找到字符串节点;它通过添加前缀而被转换。
当我们直接运行源代码时,我们也可以使用该模块。让我们理解下面的例子。
示例- 2:
import ast
from pprint import pprint
def main():
with open("ast_module.py", "r") as source:
ast_tree = ast.parse(source.read())
analysis = Analyzer()
analysis.visit(ast_tree)
analysis.report()
class Analyzer(ast.NodeVisitor):
def __init__(self):
self.stats = {"import": [], "from": []}
def node_visit(self, node):
for alias in node.names:
self.stats["import"].append(alias.name)
self.generic_visit(node)
def node_visitFrom(self, node):
for alias in node.names:
self.stats["from"].append(alias.name)
self.generic_visit(node)
def report(self):
pprint(self.stats)
if __name__ == "__main__":
输出:
{'from': ['pprint'], 'import': ['ast']}
解释-
上面的代码将 Python 文件转换为抽象语法树。然后我们分析树以获得有用的信息。
我们已经在读取模式下打开了一个 Python 文件,然后创建了一个名为 ast_tree 的 AST。然后, parse() 函数处理所有的标记,遵循所有的语言规则,并构建一个包含大量有用信息的树形数据结构。
树只不过是节点的集合,其中一个树变量引用了“根”节点。因此,我们可以访问树中的每个节点并执行操作。但是,首先,我们访问每个节点并处理数据。
分析 AST
一旦我们得到树,现在分析器遵循访问者模式。使用节点访问者类,我们可以跟踪 Python 中的任何节点。我们需要实现一个方法访问 _ <节点类型> 来访问特定的节点类型。在前面的例子中,我们使用了下面的脚本。
示例-
class Analyzer(ast.NodeVisitor):
def node_visit(self, node):
for alias in node.names:
self.stats["import"].append(alias.name)
self.generic_visit(node)
def node_visitFrom(self, node):
for alias in node.names:
self.stats["from"].append(alias.name)
self.generic_visit(node)
代码接受模块的名称,并将其存储在统计列表中。借助 NodeVisitor 类,我们可以对树进行分析。
analyzer = Analyzer()
analyzer.visit(tree)
visit()方法的工作原理与 visit_ <节点类型> 方法相同。
使用 AST 作为分析工具
Python 代码变成字节码后,人类无法读取。但它使解释器变得更快,这意味着字节码是为机器设计的,而不是为人类设计的。
AST 由足够多的结构化信息组成,这使得它们有助于学习 Python 代码。然而,ASTs 仍然不是用户友好的,但是它们比字节代码表示更容易理解。
什么时候用 Python ast
模块?
抽象语法树对代码覆盖工具很有帮助。它解析源代码,发现代码中可能存在的缺陷和错误。它也可以用在-
- 它被用作定制的 Python 解释器。
- 它用于分析静态代码。
- 它使 IDEs 变得智能,这就是所谓的智能感知。
结论
我们已经了解了 Python 中负责运行 Python 代码的 ast 模块。然后,我们从 Python 代码中构建了 AST 树,并使用节点访问者类对 AST 进行了分析。