MNTSQ Techブログ

リーガルテック・カンパニー「MNTSQ(モンテスキュー)」のTechブログです。

pythonの依存関係解析ツール、pydepsを使う

MNTSQ Tech Blog TOP > 記事一覧 > pythonの依存関係解析ツール、pydepsを使う

はじめに

皆様はpythonで書かれたソフトウェアのリアーキテクティング1をどのように進めていますでしょうか?

既存のソフトウェアに新規機能が追加しにくいとか、機能が修正しにくい等の問題がある場合にリアーキテクティングは有効です。

リアーキテクティングの初手としては既存のソフトウェアが抱える課題の洗い出しが行われます。その際にソフトウェア内のモジュール同士の依存関係を図で把握したい場面があります。

モジュール同士の依存関係が図示されていれば、モジュール同士の構造上の問題点を伝えやすくなり、かつモジュール同士の関係を将来的にどのように落としていくかも議論しやすくなります。

このような用途に用いるpython用の依存関係解析ツールとして、今回はpydepsを紹介します。

pydeps.readthedocs.io

なお、本記事で扱うコードは下記にアップロードしてあります。

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

f:id:myatsdqn:20210316191018p:plain

オプションの説明

以上が簡単な使い方の説明でした。その他にも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

f:id:myatsdqn:20210316191705p:plain

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

f:id:myatsdqn:20210318151712p:plain

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

In

pydeps package2/moduleE.py --cluster

Out

f:id:myatsdqn:20210318151321p:plain

さらにフォルダアイコン形式での表示の有無や複数のノードをクラスタとして扱うかどうかを制御するための調整用フラグがあります:

  • --max-cluster-size : フォルダアイコンとして表示されるノード数のしきい値を指定
  • --min-cluster-size:モジュールがクラスタとして扱われるノード数のしきい値を設定

実際に使ってみます

In

pydeps --cluster --min-cluster-size 2 --max-cluster-size 3 package2/moduleE.py

Out

f:id:myatsdqn:20210318152130p:plain

最後にモジュール範囲の最大ホップ数を指定する --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

f:id:myatsdqn:20210318152221p:plain

おわりに

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

この記事を書いた人

f:id:mntsq:20201223123239j:plain

yad

ビリヤニ食べたい