- 空閑康太
- 約 2,700 文字
- 3,600 View
はじめに
こんにちは、Future のインターン Engineer Camp に参加した空閑です。Python連載の9本目です。
今回のインターンではソースコード静的解析システムの開発に取り組みました。そこで本記事では、開発内容の一部である、Python の AST モジュールを使ったクラス構造の可視化について紹介します。
Python の環境構築については以下を参考にしました。
また、本記事で出てくる AST については下記を参照ください。言語やパーサは違いますが基本的な考え方は同じです。
Python の AST モジュール
Python では AST(抽象構文木)を扱うモジュールが標準ライブラリとして提供されています。まずは試しに、適当なソースコードの AST を取得してみます。
class Button: |
import ast |
出力結果(長いので折り畳み)
Module( |
ast.parse にソースコードを渡すことで AST が得られます。実際には、ツリーの根を表すAST ノードを取得することになります。ast.dump は引数に AST ノードを取り、そのノードを根とするツリーを、フォーマットした文字列として返します。
では次に、AST をたどって特定のノードに反応するコードを書いてみます。ast.NodeVisitor は AST をトラバースするための基底クラスで、このクラスを継承して独自の処理を追加します。これは Visitor パターンとなっているため、クラスごとに visit_{class_name} のメソッドを用意していきます。例えば、ソースコード内で定義されている関数名を列挙するためには、関数定義を表すノード ast.FunctionDef に反応する visit_FunctionDef メソッドを作成し、その中で関数名を表す ast.FunctionDef.name を参照します。
class MyNodeVisitor(ast.NodeVisitor): |
__init__ |
visit_{class_name} を定義することで、ast.{class_name} のノードを訪れたときのみ実行されるメソッドが作成できます。また、以下の引用のように self.generic_visit() を省略してしまうと、そのノードの子ノードは訪れることができないので注意してください。
注意して欲しいのは、専用のビジター・メソッドを具えたノードの子ノードは、このビジターが generic_visit() を呼び出すかそれ自身で子ノードを訪れない限り訪れられないということです。
https://docs.python.org/ja/3/library/ast.html#ast.NodeVisitor.generic_visit
ツール概要
今回の目標は、パッケージ・モジュール・クラスをノードとする図のようなツリーの作成です。パッケージとモジュールはディレクトリ構造にしたがってつなぎ、モジュールの下にはその中で定義されているクラスをつなぎます。作成にあたり、モジュール違いの同名クラスなどが出現することに注意します。
AST はディレクトリ構造までは表現しないため、今回は以下の手順で解析します。
- Node 定義
- ディレクトリ構造解析
- クラス定義解析
- Graphviz で可視化
1. Node 定義
解析する前に準備として Node クラスを定義しておきます。これを可視化するツリーのノードと一対一で対応させます。そして Node を継承した NodePackage, NodeModule, NodeClass を定義し、可視化に必要な情報を保持しておきます。今回はノードの識別に最小限必要なパスおよびクラス名を保持しました。これらはグラフのラベル等に使用します。
from __future__ import annotations |
2. ディレクトリ構造解析
探索対象のパス以下を再帰的に解析し、パッケージおよびモジュールのみのツリーを作成します。この時点では図のようなツリーが構築されています。
def create_module_tree(search_path: str, root: Optional[NodeRoot] = None) -> NodeRoot: |
3. クラス定義解析
各モジュールについて、クラス定義を解析していきます。NodeModule が指定するパスを読み込み、visit_ClassDef を実装した ClassDefNodeVisitor で処理します。また、ClassDefNodeVisitor には親ノードへのポインタを追加の情報として持たせています。generic_visit の前後で親ノードを入れ替えることで、クラス内クラスなども表現できます。
def create_definition_tree(root: NodeModule) -> None: |
4. Graphviz で可視化
詳細な実装はここにはあげませんが、実際に作成したツリーをトラバースしながら辺を張っていきます。同じラベルのノードはひとまとめにされてしまうので、異なるノードは異なる ID を持つように注意します。今回の実装では obj_name_full を ID として使うことができます。また、ノードの種類ごとに色を付けるのも良いでしょう。
解析結果
標準ライブラリの可視化結果を載せます。全体を載せるには大きすぎる(PDF で約 2 MB)ため、拡大しています。青がパッケージ、オレンジがモジュール、緑がクラスに対応しています。
図では http パッケージの下に、server モジュールがあり、その下にいくつかのクラスがあることが確認できます。実際、該当ディレクトリを見に行くと下図のようになっており、可視化できていることがわかります。
さいごに
今回は AST モジュールを使ってクラス構造を可視化しました。紹介した実装は静的解析の基礎となる部分であり、機能追加によって、メソッドノードの追加や型情報、コールグラフなど、より高度な情報を可視化できます。興味を持った方はぜひ、この記事から静的解析を始めていただければと思います。
インターンの感想
今回のインターンでは、プロジェクトの一員として開発に取り組みました。したがって、ミーティングやドキュメントなど、普段の個人開発ではほとんど発生しない、コミュニケーションの部分が特に重要に感じました。コミュニケーションによって文脈を共有することで、後に発生する意思決定や軌道修正などがしやすくなっていた気がします。その点では、毎日のミーティングや Slack での議論など、ご協力いただいた受け入れ先のプロジェクトの方々に感謝しています。
また、今回は静的解析ツールの開発に取り組みましたが、それでも linter や formatter など既存の静的解析ツールはかなり有用でした。デバッグにはもちろん、型ヒントやフォーマットを通してツール側からも文脈の共有が行えるため、コミュニケーションと併せてその重要性を感じました。今回のインターンは約1か月でしたが、より長期間で大規模なプロジェクトであれば、静的解析ツールはもはや必須といっても過言ではないと思います。