Python教程-Python AST模块
在本教程中,我们将学习如何使用AST来理解代码。
什么是AST模块?
AST代表抽象语法树,它是Python编程语言的一个强大工具。它允许我们与Python代码本身交互并对其进行修改。
你是否曾想过Python代码是如何运行的?它背后是否有魔法?
对于不了解的人,Python解释器负责运行Python代码。它遵循预先编写的指令,将Python代码转换为计算机可以运行的指令。
以下是将Python代码转换为机器代码的过程:
- 当我们运行代码时,代码被解析成较小的块,称为标记。这些标记是由预定义的指令创建的,应该以不同方式处理。例如,关键字else是与数字值不同的关键字。
- 这些标记存储在列表中,以构建抽象语法树(AST)。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 javatiku')")
print(code)
exec(compile(code, filename="", mode="exec"))
输出:
<_ast.Module object at 0x0000010B889A6AC0>
Hello Learner! Welcome to javatiku
评估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)))
创建多行AST
在先前的示例中,我们已经看到了单行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=[])
NodeTransformer和NodeVisitor
NodeTransformer类用于接受不同类型并根据我们的需求进行修改。ast模块还提供了NodeVisitor类,它可以在遍历树时每次调用visit函数,以便我们可以更多地控制节点,让我们理解以下示例。
示例 - 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 javatiku
解释 -
在上面的代码中,我们导入了解析代码的ast模块。然后,我们定义了Visitor类,该类继承了NodeVisitor类。每次找到字符串节点时,它会被转换并添加前缀。
我们还可以在直接运行源代码时使用模块。让我们理解以下示例。
示例 - 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
一旦我们获得树,现在分析器遵循访问者模式。使用NodeVisitor类,我们可以跟踪Python中的任何节点。我们需要实现一个visit_<节点类型>方法来访问特定节点类型。在上面的示例中,我们使用了以下脚本。
示例 -
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代码方面非常有用。然而,AST仍然不够用户友好,但比字节码表示更容易理解。
何时使用Python AST模块?
抽象语法树对于代码覆盖工具非常有帮助。它会解析源代码并查找代码中可能存在的缺陷和错误。它还可以用于以下方面:
- 用作自定义Python解释器。
- 用于分析静态代码。
- 使IDE变得智能,称为IntelliSense。
结论
我们已经了解了Python中的ast模块,它负责运行Python代码。然后,我们从Python代码构建了AST树,并使用NodeVisitor类对AST进行了分析。