目次
きっかけ
昨日参加したYAPC::Kyoto 2023。
その中のASTのトークに触発されました
いくつか例を見せていただき、「真似してやってみよう!」とモチベーションが上がりました。
一番はじめの例、「console.log
の中身の置き換え、とりあえず(すべての)文字列を置き換え」をPythonで考えてみます。
今回の参考文献
フューチャーさんの技術ブログに例を見つけ、
そこから公式のastモジュールのドキュメント
をたどって、「ast.NodeTransformer
を継承すればできそう」と感触を得ました。
できたもの:コード中のすべての文字列を置き換え
Python 3.10.9 で動かしています
% python replace_str_literal.py 1 + 2 'ham' + 'ham' print('ham') print(108) print('ham', 'ham')
文字が全部"ham"
に!
YAPCで知ったのですが、ASTをいじって文字列として出力、それをPython処理系に実行させられます!
% python replace_str_literal.py | python ham 108 ham ham
ドキュメントメモ
parse / unparse
https://docs.python.org/ja/3/library/ast.html#ast.parse
小さいコードを文字列(改行のために3連引用符使用)として定義し、parse
関数でASTに変換します。
https://docs.python.org/ja/3/library/ast.html#ast.unparse
ASTを文字列に戻すのがunparse
関数。
(実は追加されたのがPython 3.9とけっこう最近なのです)
dump
https://docs.python.org/ja/3/library/ast.html#ast.dump
主な使い道はデバッグです。
スクリプトをpython -i
で実行し、print("spam")
といったコードのASTがどうなっているか確認するのに使いました。
>>> tree = ast.parse('print("spam")') >>> print(ast.dump(tree, indent=2)) Module( body=[ Expr( value=Call( func=Name(id='print', ctx=Load()), args=[ Constant(value='spam')], keywords=[]))], type_ignores=[])
「文字列リテラルはConstant
らしいぞ」という感じで変えるべきところを見定めていきます。
NodeTransformer
https://docs.python.org/ja/3/library/ast.html#ast.NodeTransformer
NodeVisitor のサブクラスで抽象構文木を渡り歩きながらノードを変更することを許すものです。
ビジター・メソッドの戻り値が None ならば、ノードはその場から取り去られ、そうでなければ戻り値で置き換えられます。
ビジター・メソッドはself.visit_<classname>
となるそうです(親クラスのNodeVisitorのドキュメントより)。
今回はConstant
ノードを変えるのでvisit_Constant
ですね。
「定数をまとめて扱うの?」と思いましたが、親クラスのドキュメントに以下のようにもありました。
バージョン 3.8 で非推奨: visit_Num(), visit_Str(), visit_Bytes(), visit_NameConstant() および visit_Ellipsis() の各メソッドは非推奨です。(略) 全ての定数ノードを扱うには visit_Constant() を追加してください。
Constant
https://docs.python.org/ja/3/library/ast.html#ast.Constant
ドキュメントの「リテラル」のゾーンにあります。
value属性の値の型を確認して、文字列だったら置き換えるという実装をしました。
こうして、文字列を全部置き換えちゃう、やべープログラムが爆誕しました!