はじめに
皆様はpythonで書かれたソフトウェアのリアーキテクティング1をどのように進めていますでしょうか?
既存のソフトウェアに新規機能が追加しにくいとか、機能が修正しにくい等の問題がある場合にリアーキテクティングは有効です。
リアーキテクティングの初手としては既存のソフトウェアが抱える課題の洗い出しが行われます。その際にソフトウェア内のモジュール同士の依存関係を図で把握したい場面があります。
モジュール同士の依存関係が図示されていれば、モジュール同士の構造上の問題点を伝えやすくなり、かつモジュール同士の関係を将来的にどのように落としていくかも議論しやすくなります。
このような用途に用いるpython用の依存関係解析ツールとして、今回はpydepsを紹介します。
なお、本記事で扱うコードは下記にアップロードしてあります。
GitHub - UsrNameu1/PydepsSample: Sample module code to describe how to use pydeps package
インストール方法&ミニマムな使い方
グラフを図示する機能を有するため、本体のインストールの前にGraphvizのインストールが必要です。
Linux環境上であれば各ディストリのパッケージマネージャ経由で、OSXであればbrew経由で
brew install graphviz
本体はpipでインストールします。
pip3 install pydeps
使い方は
pydeps [--options] [pythonファイル名|モジュールディレクトリ名]
です。
例えば以下の構造をもったディレクトリpackage1があり、
└── package1
├── __init__.py
├── moduleA.py
└── moduleB.py
各モジュールの内容が次のようになっているとします。
moduleA.py
class A: def foo(self): pass
moduleB.py
from .moduleA import A class B: def bar(self): _ = A()
この時、ルートディレクトリ上で
pydeps package1
を実行することで、以下の図がsvgとして出力されます。
Out

オプションの説明
以上が簡単な使い方の説明でした。その他にもpydepsには循環インポートの洗い出しや他ライブラリを図示する際の細かい設定等をオプション経由で行う機能があります。
主要なオプションを表にまとめました。
| オプション | 内容 |
|---|---|
| -T FORMAT | 図の出力フォーマット. FORMAT=svg or png |
| --show-cycles | 循環インポートが発生しているパッケージのみを図示 |
| --max-bacon INT | 対象ファイル/パッケージから依存が指定したホップ以上離れたモジュールを除外する |
| --only MODULE_PATH | 図示する対象をMODULE_PATHから始まる範囲に絞れる、スペース区切りで複数指定可能 |
| --reverse | 依存の矢印を逆方向にする |
| --cluster | 後述 |
このなかのフラグを使って実際にグラフを出力してみます。
--show-cyclesフラグによる循環インポートの洗い出し
このフラグを用いて、循環インポートがあるようなパッケージを洗い出せます。先程のpackage1にさらに次の2つのモジュールが入ったとします。
moduleC.py
from .moduleD import D class C: def foo(self): _ = D()
moduleD.py
from .moduleC import C class D: def bar(self): _ = C()
--show-cycles フラグがない場合は、先程あったmoduleA, moduleBの双方も図に出力されますが、--show-cycles フラグを用いたコマンドの実行で循環インポートを含むノードのみ出力されます。
In
pydeps package1 --show-cycles
Out

cluster関連機能
他のライブラリやパッケージをimportしているコードについては--cluster フラグが有用です。
package2パッケージを作成し、その中にmoduleE.pyを作成します。
├── package1
│ ├── __init__.py
│ ├── moduleA.py
│ ├── moduleB.py
│ ├── moduleC.py
│ └── moduleD.py
└─── package2
├── __init__.py
└── moduleE.py
moduleE.py
from sklearn.feature_extraction.text import TfidfVectorizer class E: def get_vectorizer(self): return TfidfVectorizer()
その上でコマンドを実行します。
In
pydeps package2/moduleE.py
Out

この図はscikit-learnの中身の依存関係も図示していますが、--cluster フラグを使えばモジュール同士の関係をよりざっくりと描画できます
In
pydeps package2/moduleE.py --cluster
Out

さらにフォルダアイコン形式での表示の有無や複数のノードをクラスタとして扱うかどうかを制御するための調整用フラグがあります:
実際に使ってみます
In
pydeps --cluster --min-cluster-size 2 --max-cluster-size 3 package2/moduleE.py
Out

最後にモジュール範囲の最大ホップ数を指定する --max-bacon と --cluster の機能を組み合わせることで全体を俯瞰した図を見てみましょう。
├── main.py
├── package1
│ ├── __init__.py
│ ├── moduleA.py
│ ├── moduleB.py
│ ├── moduleC.py
│ └── moduleD.py
└── package2
├── __init__.py
├── moduleE.py
├── moduleF.py
└── moduleH.py
main.py
from package2.moduleH import H if __name__ == '__main__': _ = H()
moduleF.py
from package1.moduleB import B from package1.moduleC import C class F: def foo(self): _ = B() _ = C()
moduleH.py
from .moduleF import F from .moduleE import E class H: def bar(self): _ = E() _ = F()
In
pydeps --cluster --max-bacon 5 --min-cluster-size 2 --max-cluster-size 4 main.py
Out

おわりに
この記事ではpydepsを用いてpythonコードの依存関係を図示する方法を紹介しました。本記事で紹介しきれなかった機能として設定ファイルの読み込みや、依存グラフのテキスト形式での出力等もあり、掘っていくとCI等の開発プロセスにも組み込めそうです。
この記事を書いた人
yad
ビリヤニ食べたい