はじめに
七尾百合子さん、お誕生日 335日目 おめでとうございます! nikkieです。
抽象構文木の話です。
ast.NodeVisitorについて発見がありました。
目次
nikkie とast.NodeVisitor
NodeVisitorには大変お世話になっております。
継承して田中琴葉ちゃんを作り出すほどです
class ArgumentConcreteTypeHintChecker(ast.NodeVisitor): def visit_arg(self, node): # 省略
こちらはNodeVisitorを継承したNodeTransformerを私の方で継承した例
class StrNodeTransformer(ast.NodeTransformer): def visit_Constant(self, node: ast.Constant): # 省略
このたび改めてドキュメントを読みました。
ast.NodeVisitorのドキュメント
https://docs.python.org/ja/3/library/ast.html#ast.NodeVisitor
抽象構文木を渡り歩いてビジター関数を見つけたノードごとに呼び出すノード・ビジターの基底クラスです。
2つのメソッドを持ちます
visit(node)デフォルトの実装では
self.visit_*classname*というメソッド (ここで classname はノードのクラス名です) を呼び出すか、そのメソッドがなければgeneric_visit()を呼び出します。
generic_visit(node)このビジターはノードの全ての子について
visit()を呼び出します。
ここを確認して、上述の例でvisit_argやvisit_Constantを定義した理由が今までよりも分かりました。
ArgumentConcreteTypeHintChecker().visit(tree)- -> Module node について
visit_Moduleはないので、generic_visit()呼び出し - ->
treeの子についてvisit()呼び出し
再帰的に子を呼び出していき、arg node に至ったら、私が実装したvisit_arg()が呼び出されます!
visit_arg()では最後にgeneric_visit()を呼んでいるので、子についてvisit()呼び出しに戻るのですね
class ArgumentConcreteTypeHintChecker(ast.NodeVisitor): def visit_arg(self, node): # 省略 self.generic_visit(node)
さらに実装を覗いたところ、思ったよりも少ない行数で実装されているという発見がありました。
ast.NodeVisitorの実装
https://github.com/python/cpython/blob/v3.14.3/Lib/ast.py#L482-L516
class NodeVisitor(object): def visit(self, node): method = 'visit_' + node.__class__.__name__ visitor = getattr(self, method, self.generic_visit) return visitor(node) def generic_visit(self, node): for field, value in iter_fields(node): if isinstance(value, list): for item in value: if isinstance(item, AST): self.visit(item) elif isinstance(value, AST): self.visit(value)
ドキュメントで確認したとおりです!
visit()のvisitor = getattr(self, method, self.generic_visit)がまさにそれ- node 向けの
visit_*classname*が見つからなければgeneric_visit()を呼び出します - 動的型付けの Python らしい実装だと思いました
- node 向けの
generic_visit()はフィールドのそれぞれ(子と理解)に対してvisit()を再帰的に呼び出しています
以前 pyflakes の visitor の実装を見ていて、「めちゃ長く書くんだな」と思っていたので、NodeVisitorの実装が短かったのは驚きでした
終わりに
ast.NodeVisitorのドキュメントや実装を見て、今まで書いていた実装の理解が深まりました。
visit()でvisit_*classname*を呼び出すが、見つからなければgeneric_visit()を呼び出す- だから
NodeVisitorを継承したクラスでは、visit_*classname*を実装する必要があった
- だから
generic_visit()で AST のノードの子に対してvisit()を呼び出す(再帰)NodeVisitorを継承したクラスのvisit_*classname*では、generic_visit(node)を呼び出して再帰的な処理とする必要があった
実装を見てドキュメントの理解も深まりました。
rustpython-ast の visitor とは少し違うのも面白いですね。
(visit_*classname*とgeneric_visit_*classname*がある)