在本教程中,我们将学习如何使用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进行了分析。

标签: Tkinter教程, Tkinter安装, Tkinter库, Tkinter入门, Tkinter学习, Tkinter入门教程, Tkinter, Tkinter进阶, Tkinter指南, Tkinter学习指南, Tkinter进阶教程, Tkinter编程