ast --- 抽象構文木

ソースコード: Lib/ast.py


ast モジュールは、Python アプリケーションで Python の抽象構文木を処理しやすくするものです。抽象構文そのものは、Python のリリースごとに変化する可能性があります。このモジュールを使用すると、現在の文法をプログラム上で知る助けになるでしょう。

抽象構文木を作成するには、 ast.PyCF_ONLY_AST を組み込み関数 compile() のフラグとして渡すか、あるいはこのモジュールで提供されているヘルパー関数 parse() を使います。その結果は、 ast.AST を継承したクラスのオブジェクトのツリーとなります。抽象構文木は組み込み関数 compile() を使って Python コード・オブジェクトにコンパイルすることができます。

Node クラス

class ast.AST

このクラスは全ての AST ノード・クラスの基底です。実際のノード・クラスは 後ほど 示す Parser/Python.asdl ファイルから派生したものです。これらのクラスは _ast C モジュールで定義され、 ast にもエクスポートし直されています。

抽象文法の左辺のシンボル一つずつにそれぞれ一つのクラスがあります (たとえば ast.stmtast.expr)。それに加えて、右辺のコンストラクタ一つずつにそれぞれ一つのクラスがあり、これらのクラスは左辺のツリーのクラスを継承しています。たとえば、 ast.BinOpast.expr から継承しています。代替を伴った生成規則 (production rules with alternatives) (別名 "sums") の場合、左辺は抽象クラスとなり、特定のコンストラクタ・ノードのインスタンスのみが作成されます。

_fields

各具象クラスは属性 _fields を持っており、すべての子ノードの名前をそこに保持しています。

具象クラスのインスタンスは、各子ノードに対してそれぞれひとつの属性を持っています。この属性は、文法で定義された型となります。たとえば ast.BinOp のインスタンスは left という属性を持っており、その型は ast.expr です。

これらの属性が、文法上 (クエスチョンマークを用いて) オプションであるとマークされている場合は、その値が None となることもあります。属性が0個以上の複数の値をとりうる場合 (アスタリスクでマークされている場合) は、値は Python のリストで表されます。全ての属性は AST を compile() でコンパイルする際には存在しなければならず、そして妥当な値でなければなりません。

lineno
col_offset
end_lineno
end_col_offset

Instances of ast.expr and ast.stmt subclasses have lineno, col_offset, lineno, and col_offset attributes. The lineno and end_lineno are the first and last line numbers of source text span (1-indexed so the first line is line 1) and the col_offset and end_col_offset are the corresponding UTF-8 byte offsets of the first and last tokens that generated the node. The UTF-8 offset is recorded because the parser uses UTF-8 internally.

コンパイラは終了位置を必要としないことに注意してください。このため終了位置は省略可能です。終了位置を示すオフセットは最後のシンボルの 後の位置 になります。例えば一行で書かれた式のソースコードのセグメントは 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.NameConstant そして ast.Ellipsis は現バージョンまで使用可能ですが、将来のPythonリリースで削除される予定です。削除されるまでの間、これらをインスタンス化すると、異なるクラスのインスタンスが返されます。

抽象文法 (Abstract Grammar)

抽象文法は、現在次のように定義されています:

-- 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)

source を解析して AST ノードにします。compile(source, filename, mode, ast.PyCF_ONLY_AST) と等価です。

type_comments=True が与えられると、パーサは PEP 484 および PEP 526 で規定された型コメントをチェックし、返すように修正されます。これは ast.PyCF_TYPE_COMMENTS を追加したフラグを compile() に渡すことと等価です。パーサは不適切な場所に配置された型コメントに対してシンタックスエラーをレポートします。 このフラグがない場合、型コメントは無視されて AST ノードの type_comment フィールドは常に None になります。さらに、 # type: ignore コメントの位置は Moduletype_ignores 属性として返されます (それ以外の場合は常に空のリストになります)。

さらに mode'func_type' の場合、入力構文は、たとえば (str, int) -> List[str] のような PEP 484 の "シグネチャ型コメント (signature type comments)" に対応するように修正されます。

また、 feature_version(major, minor) のタプルに設定すると、パーサは指定された Python バージョンの文法で構文解析を試みます。今のところ major3 でなければなりません。たとえば、 feature_version=(3, 4) と設定すると asyncawait を変数名として使うことが可能になります。 サポートされている最低のバージョンは (3, 4); 最高のバージョンは sys.version_info[0:2] です。

警告

十分に大きい文字列や複雑な文字列によって Python の抽象構文木コンパイラのスタックの深さの限界を越えることで、 Python インタプリタをクラッシュさせることができます。

バージョン 3.8 で変更: type_commentsmode='func_type'``feature_version``が追加されました。

ast.literal_eval(node_or_string)

式ノードまたは Python のリテラルまたはコンテナのディスプレイ表現を表す文字列を安全に評価します。与えられる文字列またはノードは次のリテラルのみからなるものに限られます: 文字列、バイト列、数、タプル、リスト、辞書、集合、ブール値、 None

この関数は Python の式を含んだ信頼出来ない出どころからの文字列を、値自身を解析することなしに安全に評価するのに使えます。この関数は、例えば演算や添え字を含んだ任意の複雑な表現を評価するのには使えません。

警告

十分に大きい文字列や複雑な文字列によって Python の抽象構文木コンパイラのスタックの深さの限界を越えることで、 Python インタプリタをクラッシュさせることができます。

バージョン 3.2 で変更: バイト列リテラルと集合リテラルが受け取れるようになりました。

ast.get_docstring(node, clean=True)

与えられた node (これは FunctionDef, AsyncFunctionDef, ClassDef, Module のいずれかのノードでなければなりません) のドキュメント文字列を返します。もしドキュメント文字列が無ければ None を返します。 clean が真ならば、ドキュメント文字列のインデントを inspect.cleandoc() を用いて一掃します。

バージョン 3.5 で変更: AsyncFunctionDef がサポートされました。

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

source のうちで node を生成したソースコードのセグメントを取得します。位置情報 (lineno, end_lineno, col_offset, または end_col_offset) が欠けている場合 None を返します。

paddedTrue の場合、複数行にわたる文の最初の行が元の位置に一致するように空白文字でパディングされます。

バージョン 3.8 で追加.

ast.fix_missing_locations(node)

compile() はノード・ツリーをコンパイルする際、 linenocol_offset 両属性をサポートする全てのノードに対しそれが存在するものと想定します。生成されたノードに対しこれらを埋めて回るのはどちらかというと退屈な作業なので、このヘルパーが再帰的に二つの属性がセットされていないものに親ノードと同じ値をセットしていきます。再帰の出発点が node です。

ast.increment_lineno(node, n=1)

node で始まるツリー内の各ノードの行番号と終了行番号を n ずつ増やします。これはファイルの中で別の場所に "コードを移動する" ときに便利です。

ast.copy_location(new_node, old_node)

ソースの場所 (lineno, col_offset, end_lineno, および end_col_offset) を old_node から new_node に可能ならばコピーし、 new_node を返します。

ast.iter_fields(node)

node にある node._fields のそれぞれのフィールドを (フィールド名, 値) のタプルとして yield します。

ast.iter_child_nodes(node)

node の直接の子ノード全てを yield します。すなわち、yield されるのは、ノードであるような全てのフィールドおよびノードのリストであるようなフィールドの全てのアイテムです。

ast.walk(node)

node の全ての子孫ノード(node 自体を含む)を再帰的に yield します。順番は決められていません。この関数はノードをその場で変更するだけで文脈を気にしないような場合に便利です。

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)を渡り歩き、ビジター・メソッドの戻り値を使って古いノードを置き換えたり削除したりします。ビジター・メソッドの戻り値が None ならば、ノードはその場から取り去られ、そうでなければ戻り値で置き換えられます。置き換えない場合は戻り値が元のノードそのものであってもかまいません。

それでは例を示しましょう。Name (たとえば foo) を見つけるたび全て data['foo'] に書き換える変換器 (transformer) です:

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)

node 内のツリーのフォーマットされたダンプを返します。主な使い道はデバッグです。 annotate_fields が(デフォルトで)trueの場合、返される文字列はフィールドの名前と値を示します。 annotate_fields がfalseの場合、あいまいさのないフィールド名を省略することにより、結果文字列はよりコンパクトになります。行番号や列オフセットのような属性はデフォルトではダンプされません。これがほ欲しければ、 include_attributes をtrueにセットすることができます。

参考

外部ドキュメント Green Tree Snakes には Python AST についての詳細が書かれています。

ASTTokens は Python AST を、生成元のソースコードのトークン位置やテキストで注解します。これはソースコード変換を行うツールで有用です。

leoAst.py unifies the token-based and parse-tree-based views of python programs by inserting two-way links between tokens and ast nodes.

LibCST はコードを ast ツリーに似た構文木 (Concrete Syntax Tree) にパースし、かつ全ての書式設定の詳細を保持します。これは自動リファクタリングアプリケーション (codemod) やリンタを作成する際に有用です。

Parso はエラーリカバリや異なる Python バージョン (複数の Python バージョン) での復元可能なパース (round-trip parsing) をサポートします。また、 Parso は Python ファイル内の複数の文法エラーをリストすることもできます。