您的位置:

Python ast模块

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