Pythonを解読する:抽象構文木(AST)を使ってコードを理解する方法


プログラミングについて少しメタ化しましょう.
どのようにPythonプログラム(より良いインタプリタとして知っている)“どのようにコードを実行する”を知っていますか?あなたがプログラミングに新しいならば、それは魔法のように見えるかもしれません.実際には、それはまだ私に10年以上の専門家である後に魔法のようです.
Pythonインタプリタは魔法ではありません.それはあなたのコードを機械が動くことができる指示に翻訳するステップの予測可能なセットに続きます.
かなり高いレベルで、あなたのコードに起こることは以下の通りです.
  • コードは通常トークンと呼ばれる部分のリストに解析されます.これらのトークンは、異なる扱い方をするためのルールのセットに基づいています.例えば、キーワードif のような数値より異なるトークンです42 .
  • トークンの生のリストは抽象的な構文木、ASTを構築するために変換されます.ASTはPython言語の文法に基づいてリンクされたノードの集まりです.私たちが瞬間的にそれ以上の光を輝かせるので、それが今意味をなさないならば、心配しないでください.
  • 抽象構文木から、インタプリタはバイトコードと呼ばれる低いレベルの命令を生成できます.これらの指示はBINARY_ADD そして、コンピュータがそれらを走らせることができるように、非常に一般的であるはずです.
  • バイトコード命令が利用できるので、インタプリタは最終的にコードを実行できます.バイトコードは、プログラムを実行するためにCPUとメモリと最終的に対話するオペレーティングシステムの関数を呼び出すために使用されます.
  • その詳細についてはもっと詳細に説明することができましたが、それはどのように入力された文字がコンピュータのCPUによって実行されるかのラフスケッチです.

    解析ツールとしてのAST
    あなたのソースコードがバイトコードに変えられる時までには、あなたが書いたものについての理解を得るには遅すぎる.バイトコードは非常に原始的であり、非常に高速のインタプリタを作るために調整されます.言い換えると、バイトコードは人々の上でコンピュータのために設計されます.
    一方、抽象構文木は、コードの学習に役立つように十分な構造情報を持っています.しかし、彼らはバイトコード表現より賢明です.
    Pythonは“バッテリー含まれている”言語であるため、あなたが使用する必要があるツールは、標準ライブラリに組み込まれています.
    ASTで動作する主なツールはast モジュールです.例を見てみましょう.
    ast 例によって
    以下はthe example Python script それを使用します.このスクリプトは“モジュールがインポートされたものの質問に答えますか?”
    import ast
    from pprint import pprint
    
    
    def main():
        with open("ast_example.py", "r") as source:
            tree = ast.parse(source.read())
    
        analyzer = Analyzer()
        analyzer.visit(tree)
        analyzer.report()
    
    
    class Analyzer(ast.NodeVisitor):
        def __init__(self):
            self.stats = {"import": [], "from": []}
    
        def visit_Import(self, node):
            for alias in node.names:
                self.stats["import"].append(alias.name)
            self.generic_visit(node)
    
        def visit_ImportFrom(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__":
        main()
    
    このコードはいくつかの主要な事柄を行います.
  • Pythonファイルのテキスト(この場合、例のコードそのもの)を抽象構文木に変換します.
  • ASTを分析して情報を抽出します.
  • 次のコードを実行できます.
    $ python3 ast_example.py
    {'from': ['pprint'], 'import': ['ast']}
    

    に変換
    with open("ast_example.py", "r") as source:
        tree = ast.parse(source.read())
    
    2行のコードで、ファイルを読んで、ASTという名前を作成しますtree . The ast.parse 機能は、このスナップになります!その機能のフードの下で、我々は至福を無視することができるトンが起こっている.
    つの関数呼び出しで、Pythonはすべてのトークンを処理し、言語のすべての規則に従って、コードを実行するために関連するすべての情報を含むデータ構造を構築しました.
    移動する前に、木が何であるかについて考えるために、ちょっと時間を計ろう.木はソフトウェア開発における非常に深い話題であるので、これは徹底的な説明よりむしろプライマーを考慮します.

    A tree is a way to hold data as a set of "nodes" connected by "edges."


             +-----+
             |  A  |
             +-----+
            /       \
           /         \
    +-----+           +-----+
    |  B  |           |  C  |
    +-----+           +-----+
    
    この図において、A,B,Cは全てノードであり、A〜BとA〜Cとを接続する辺がある.
    このツリーをコードで表現する一つの方法は次のようになります.
    class Node:
        def __init__(self, value):
            self.value = value
            self.children = []
    
    tree = Node('A')
    tree.children.append(Node('B'))
    tree.children.append(Node('C'))
    
    注意tree 実際にノードです!木で作業するとき、私たちは本当にノードのコレクションを扱っています、そして、木の変数は「ルート」ノード(例えば、ノードA)へのリファレンスです.この種の構造を持つことによって、木の各ノードをチェックして、行動を起こすことができます.ツリー内の各ノードを訪問し、そのデータを処理することによって行う.
    def print_node_value(value):
        print(value)
    
    def visit(node, handle_node):
        handle_node(node.value)
        for child in node.children:
            visit(child, handle_node)
    
    # tree is from the previous example.
    visit(tree, print_node_value)
    # This should print:
    # A
    # B
    # C
    
    木が何であるかという考えがあるので、我々は例のスクリプトの次のセクションが何をするかについて考慮することができます.Python抽象構文木のツリー構造は、ノード数とデータの型のためにより関係がありますが、ノードとエッジのコアアイデアは同じです.

    分析する
    一度、私たちは木を持っているAnalyzer ツリーから情報を抽出するために上記の訪問者パターンに従います.
    私は、Python ASTが私の基本より複雑である点に注意しましたNode デザイン.つの違いは、それが様々な種類のノードを追跡するということです.ここはどこですast.NodeVisitor が便利です.
    エーNodeVisitor Python AST内の任意のノードに対応できます.特定の種類のノードを訪問するには、以下のような方法を実装しなければなりませんvisit_<node type> .
    私の例のコードはインポートについて調べることです.インポートについて学ぶために、コードはImport and ImportFrom ノードの種類.
    def visit_Import(self, node):
        for alias in node.names:
            self.stats["import"].append(alias.name)
        self.generic_visit(node)
    
    def visit_ImportFrom(self, node):
        for alias in node.names:
            self.stats["from"].append(alias.name)
        self.generic_visit(node)
    
    このコードは、モジュールの名前を受け取り、統計情報の一覧に格納します.コードが空想でない間、ASTノードと対話する方法を示します.
    NodeVisitor クラスを定義すると、ツリーを解析するために使用できます.
    analyzer = Analyzer()
    analyzer.visit(tree)
    
    The visit メソッドはvisit_<node type> ツリー構造を横切って横切っている間、そのタイプのノードが遭遇するときはいつでも、メソッド.
    では、どのような種類のノード型が存在するのでしょうか?あなたは完全なリストを見つけることができますAbstract Grammar セクションast モジュールドキュメント.正直、私はそのドキュメントを少し吸収するのが難しいとわかります.あなたのようなより徹底的なガイドを参照してより多くの成功を持っている可能性がありますGreen Tree Snakes ノードガイド.

    ラッピング
    さて、どうすればいいのでしょうか
  • PythonソースコードからASTをビルドします.
  • Aを用いたAST解析NodeVisitor .
  • 抽象構文木を使って、あなたのコードについて多くの興味深い質問に答えることができます.次のようになります.
  • どのように多くの変数を使用しましたか?
  • 私のコードで最も一般的な関数呼び出しは何ですか?
  • 私のモジュールはしっかりとお互いに結合していますか?
  • どのサードパーティライブラリが異なるパッケージで頻繁に表示されますか?
  • The ast モジュールはおそらく非常に頻繁に到達するツールではありません.必要な時にはast , その最小限のAPIはかなり記憶に残るとすぐにコードを分析することができます.
    あなたがこの役に立つとわかるならば、あなたはTwitterまたはあなたの大好きな社会的メディア・サイトでこれを共有したいですか?私はこれらのトピックについての人々とチャットするのが好きですので、私をさえずること自由に感じてください.