ast — 抽象语法树 — Python 文档

来自菜鸟教程
Python/docs/3.8/library/ast
跳转至:导航、​搜索

ast — 抽象语法树

源代码: :source:`Lib/ast.py`



ast 模块帮助 Python 应用程序处理 Python 抽象语法语法树。 抽象语法本身可能会随着每个 Python 版本而改变; 该模块有助于以编程方式找出当前语法的样子。

可以通过将 ast.PyCF_ONLY_AST 作为标志传递给 compile() 内置函数,或使用本文中提供的 parse() 助手来生成抽象语法树模块。 结果将是一个对象树,其类都继承自 ast.AST。 可以使用内置的 compile() 函数将抽象语法树编译成 Python 代码对象。

节点类

class ast.AST

这是所有 AST 节点类的基础。 实际的节点类派生自 Parser/Python.asdl 文件,该文件在 下面复制 。 它们在 _ast C 模块中定义并在 ast 中重新导出。

为抽象语法中的每个左侧符号定义了一个类(例如,ast.stmtast.expr)。 此外,右侧为每个构造函数定义了一个类; 这些类继承自左侧树的类。 例如,ast.BinOp 继承自 ast.expr。 对于带有替代项(又名“总和”)的产生式规则,左侧的类是抽象的:只创建特定构造函数节点的实例。

_fields

每个具体类都有一个属性 _fields,它给出了所有子节点的名称。

具体类的每个实例的每个子节点都有一个属性,其类型在语法中定义。 例如,ast.BinOp 实例具有 ast.expr 类型的属性 left

如果这些属性在语法中标记为可选(使用问号),则值可能是 None。 如果属性可以有零个或多个值(用星号标记),则这些值表示为 Python 列表。 使用 compile() 编译 AST 时,所有可能的属性都必须存在并具有有效值。

lineno
col_offset
end_lineno
end_col_offset

ast.exprast.stmt 子类的实例具有 linenocol_offsetlineno 和 col_offset] 属性X12 . linenoend_lineno 是源文本跨度的第一个和最后一个行号(1 索引,所以第一行是第 1 行)和 col_offset 和 [ X172X]end_col_offset 是生成节点的第一个和最后一个令牌的相应 UTF-8 字节偏移量。 记录 UTF-8 偏移量是因为解析器在内部使用 UTF-8。

请注意,编译器不需要结束位置,因此是可选的。 结束偏移量是最后一个符号之后的,例如可以使用source_line[node.col_offset : node.end_col_offset]得到一个单行表达式节点的源段。

ast.T 的构造函数解析其参数如下:

  • 如果有位置参数,则必须与T._fields中的项一样多; 它们将被分配为这些名称的属性。

  • 如果有关键字参数,它们会将同名的属性设置为给定的值。

例如,要创建和填充 ast.UnaryOp 节点,您可以使用

node = ast.UnaryOp()
node.op = ast.USub()
node.operand = ast.Constant()
node.operand.value = 5
node.operand.lineno = 0
node.operand.col_offset = 0
node.lineno = 0
node.col_offset = 0

或者更紧凑

node = ast.UnaryOp(ast.USub(), ast.Constant(5, lineno=0, col_offset=0),
                   lineno=0, col_offset=0)

在 3.8 版更改:ast.Constant 现在用于所有常量。


自 3.8 版起已弃用:旧类 ast.Numast.Strast.Bytesast.NameConstantast.Ellipsis 仍然可用,但它们将在未来的 Python 版本中删除。 同时,实例化它们将返回不同类的实例。


抽象语法

抽象语法目前定义如下:

-- ASDL's 5 builtin types are:
-- identifier, int, string, object, constant

module Python
{
    mod = Module(stmt* body, type_ignore *type_ignores)
        | Interactive(stmt* body)
        | Expression(expr body)
        | FunctionType(expr* argtypes, expr returns)

        -- not really an actual node but useful in Jython's typesystem.
        | Suite(stmt* body)

    stmt = FunctionDef(identifier name, arguments args,
                       stmt* body, expr* decorator_list, expr? returns,
                       string? type_comment)
          | AsyncFunctionDef(identifier name, arguments args,
                             stmt* body, expr* decorator_list, expr? returns,
                             string? type_comment)

          | ClassDef(identifier name,
             expr* bases,
             keyword* keywords,
             stmt* body,
             expr* decorator_list)
          | Return(expr? value)

          | Delete(expr* targets)
          | Assign(expr* targets, expr value, string? type_comment)
          | AugAssign(expr target, operator op, expr value)
          -- 'simple' indicates that we annotate simple name without parens
          | AnnAssign(expr target, expr annotation, expr? value, int simple)

          -- use 'orelse' because else is a keyword in target languages
          | For(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)
          | AsyncFor(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)
          | While(expr test, stmt* body, stmt* orelse)
          | If(expr test, stmt* body, stmt* orelse)
          | With(withitem* items, stmt* body, string? type_comment)
          | AsyncWith(withitem* items, stmt* body, string? type_comment)

          | Raise(expr? exc, expr? cause)
          | Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)
          | Assert(expr test, expr? msg)

          | Import(alias* names)
          | ImportFrom(identifier? module, alias* names, int? level)

          | Global(identifier* names)
          | Nonlocal(identifier* names)
          | Expr(expr value)
          | Pass | Break | Continue

          -- XXX Jython will be different
          -- col_offset is the byte offset in the utf8 string the parser uses
          attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)

          -- BoolOp() can use left & right?
    expr = BoolOp(boolop op, expr* values)
         | NamedExpr(expr target, expr value)
         | BinOp(expr left, operator op, expr right)
         | UnaryOp(unaryop op, expr operand)
         | Lambda(arguments args, expr body)
         | IfExp(expr test, expr body, expr orelse)
         | Dict(expr* keys, expr* values)
         | Set(expr* elts)
         | ListComp(expr elt, comprehension* generators)
         | SetComp(expr elt, comprehension* generators)
         | DictComp(expr key, expr value, comprehension* generators)
         | GeneratorExp(expr elt, comprehension* generators)
         -- the grammar constrains where yield expressions can occur
         | Await(expr value)
         | Yield(expr? value)
         | YieldFrom(expr value)
         -- need sequences for compare to distinguish between
         -- x < 4 < 3 and (x < 4) < 3
         | Compare(expr left, cmpop* ops, expr* comparators)
         | Call(expr func, expr* args, keyword* keywords)
         | FormattedValue(expr value, int? conversion, expr? format_spec)
         | JoinedStr(expr* values)
         | Constant(constant value, string? kind)

         -- the following expression can appear in assignment context
         | Attribute(expr value, identifier attr, expr_context ctx)
         | Subscript(expr value, slice slice, expr_context ctx)
         | Starred(expr value, expr_context ctx)
         | Name(identifier id, expr_context ctx)
         | List(expr* elts, expr_context ctx)
         | Tuple(expr* elts, expr_context ctx)

          -- col_offset is the byte offset in the utf8 string the parser uses
          attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)

    expr_context = Load | Store | Del | AugLoad | AugStore | Param

    slice = Slice(expr? lower, expr? upper, expr? step)
          | ExtSlice(slice* dims)
          | Index(expr value)

    boolop = And | Or

    operator = Add | Sub | Mult | MatMult | Div | Mod | Pow | LShift
                 | RShift | BitOr | BitXor | BitAnd | FloorDiv

    unaryop = Invert | Not | UAdd | USub

    cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn

    comprehension = (expr target, expr iter, expr* ifs, int is_async)

    excepthandler = ExceptHandler(expr? type, identifier? name, stmt* body)
                    attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)

    arguments = (arg* posonlyargs, arg* args, arg? vararg, arg* kwonlyargs,
                 expr* kw_defaults, arg? kwarg, expr* defaults)

    arg = (identifier arg, expr? annotation, string? type_comment)
           attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)

    -- keyword arguments supplied to call (NULL identifier for **kwargs)
    keyword = (identifier? arg, expr value)

    -- import name with optional 'as' alias.
    alias = (identifier name, identifier? asname)

    withitem = (expr context_expr, expr? optional_vars)

    type_ignore = TypeIgnore(int lineno, string tag)
}

ast 助手

除了节点类之外,ast 模块定义了这些用于遍历抽象语法树的实用函数和类:

ast.parse(source, filename='<unknown>', mode='exec', *, type_comments=False, feature_version=None)

将源解析为 AST 节点。 相当于 compile(source, filename, mode, ast.PyCF_ONLY_AST)

如果给出 type_comments=True,则修改解析器以检查和返回 PEP 484PEP 526[ X146X]。 这相当于将 ast.PyCF_TYPE_COMMENTS 添加到传递给 compile() 的标志。 这将报告错位类型注释的语法错误。 如果没有这个标志,类型注释将被忽略,所选 AST 节点上的 type_comment 字段将始终为 None。 另外,# type: ignore评论的位置会作为Moduletype_ignores属性返回(否则总是空列表)。

另外,如果mode'func_type',则修改输入语法以对应PEP 484“签名类型注释”,例如 (str, int) -> List[str]

此外,将 feature_version 设置为元组 (major, minor) 将尝试使用该 Python 版本的语法进行解析。 当前 major 必须等于 3。 例如,设置 feature_version=(3, 4) 将允许使用 asyncawait 作为变量名。 支持的最低版本是(3, 4); 最高的是sys.version_info[0:2]

警告

由于 Python 的 AST 编译器中的堆栈深度限制,有可能使用足够大/复杂的字符串使 Python 解释器崩溃。

3.8 版更改: 添加 type_commentsmode='func_type'feature_version

ast.literal_eval(node_or_string)

安全地计算包含 Python 文字或容器显示的表达式节点或字符串。 提供的字符串或节点只能由以下 Python 文字结构组成:字符串、字节、数字、元组、列表、字典、集合、布尔值和 None

这可用于安全地评估包含来自不受信任来源的 Python 值的字符串,而无需自己解析这些值。 它不能评估任意复杂的表达式,例如涉及运算符或索引。

警告

由于 Python 的 AST 编译器中的堆栈深度限制,有可能使用足够大/复杂的字符串使 Python 解释器崩溃。

3.2 版更改: 现在允许字节和设置文字。

ast.get_docstring(node, clean=True)

返回给定 node(必须是 FunctionDefAsyncFunctionDefClassDefModule 节点)的文档字符串,或None 如果它没有文档字符串。 如果 clean 为真,则使用 inspect.cleandoc() 清理文档字符串的缩进。

3.5 版更改:现在支持 AsyncFunctionDef

ast.get_source_segment(source, node, *, padded=False)

获取生成 nodesource 的源代码段。 如果缺少某些位置信息(linenoend_linenocol_offsetend_col_offset),则返回 None

如果 paddedTrue,多行语句的第一行将用空格填充以匹配其原始位置。

3.8 版中的新功能。

ast.fix_missing_locations(node)
当您使用 compile() 编译节点树时,编译器期望每个支持它们的节点具有 linenocol_offset 属性。 填充生成的节点相当繁琐,因此该助手通过将这些属性设置为父节点的值,在尚未设置的地方递归添加这些属性。 它从 节点 开始递归地工作。
ast.increment_lineno(node, n=1)
将树中从 node 开始的每个节点的行号和结束行号增加 n。 这对于将代码“移动”到文件中的不同位置很有用。
ast.copy_location(new_node, old_node)
将源位置(linenocol_offsetend_linenoend_col_offset)从 old_node 复制到 new_node,如果可能,并返回 new_node
ast.iter_fields(node)
节点 上存在的 node._fields 中的每个字段生成一个 (fieldname, value) 元组。
ast.iter_child_nodes(node)
生成 node 的所有直接子节点,即作为节点的所有字段和作为节点列表的所有字段项。
ast.walk(node)
递归生成树中从 node 开始的所有后代节点(包括 node 本身),没有指定的顺序。 如果您只想就地修改节点而不关心上下文,这将非常有用。
class ast.NodeVisitor

一个节点访问者基类,它遍历抽象语法树并为找到的每个节点调用一个访问者函数。 此函数可能会返回一个值,该值由 visit() 方法转发。

此类旨在被子类化,子类添加访问者方法。

visit(node)

访问一个节点。 默认实现调用名为 self.visit_classname 的方法,其中 classname 是节点类的名称,如果该方法不存在,则调用 generic_visit()

generic_visit(node)

此访问者在节点的所有子节点上调用 visit()

请注意,除非访问者调用 generic_visit() 或自己访问它们,否则不会访问具有自定义访问者方法的节点的子节点。

如果要在遍历期间对节点应用更改,请不要使用 NodeVisitor。 为此,存在一个允许修改的特殊访问者 (NodeTransformer)。

自 3.8 版起已弃用:方法 visit_Num()visit_Str()visit_Bytes()visit_NameConstant()visit_Ellipsis() 现在已弃用,并且在未来的 Python 版本中不会被调用。 添加 visit_Constant() 方法来处理所有常量节点。

class ast.NodeTransformer

NodeVisitor 子类,它遍历抽象语法树并允许修改节点。

NodeTransformer 将遍历 AST 并使用访问者方法的返回值来替换或删除旧节点。 如果visitor方法的返回值为None,节点会从其所在位置移除,否则用返回值替换。 返回值可能是原始节点,在这种情况下不会发生替换。

这是一个示例转换器,它将所有出现的名称查找 (foo) 重写为 data['foo']

class RewriteName(NodeTransformer):

    def visit_Name(self, node):
        return Subscript(
            value=Name(id='data', ctx=Load()),
            slice=Index(value=Constant(value=node.id)),
            ctx=node.ctx
        )

请记住,如果您正在操作的节点具有子节点,则您必须自己转换子节点或首先为该节点调用 generic_visit() 方法。

对于作为语句集合的一部分(适用于所有语句节点)的节点,访问者还可以返回节点列表而不仅仅是单个节点。

如果 NodeTransformer 引入新节点(不属于原始树的一部分)而不提供位置信息(例如 lineno),则应调用 fix_missing_locations()重新计算位置信息的新子树:

tree = ast.parse('foo', mode='eval')
new_tree = fix_missing_locations(RewriteName().visit(tree))

通常你像这样使用变压器:

node = YourTransformer().visit(node)
ast.dump(node, annotate_fields=True, include_attributes=False)
返回 节点 中树的格式化转储。 这主要用于调试目的。 如果 annotate_fields 为真(默认情况下),返回的字符串将显示字段的名称和值。 如果 annotate_fields 为 false,则通过省略明确的字段名称,结果字符串将更加紧凑。 默认情况下不会转储行号和列偏移等属性。 如果需要,可以将 include_attributes 设置为 true。

也可以看看

Green Tree Snakes 是一个外部文档资源,其中包含有关使用 Python AST 的详细信息。

ASTTokens 使用生成它们的源代码中的标记和文本的位置来注释 Python AST。 这对于进行源代码转换的工具很有帮助。

leoAst.py 通过在令牌和 ast 节点之间插入双向链接,统一了 Python 程序的基于令牌和基于解析树的视图。

LibCST 将代码解析为一个具体的语法树,它看起来像一个 ast 树并保留所有格式细节。 它对于构建自动重构 (codemod) 应用程序和 linter 很有用。

Parso 是一个 Python 解析器,支持不同 Python 版本(在多个 Python 版本中)的错误恢复和往返解析。 Parso 还能够在您的 python 文件中列出多个语法错误。