[python] クラス図を自動生成する

## モチベーション
プルリクもらったときに，ざっと構造を理解しつつ，循環参照していないかをチェックするため，クラス図を自動作成したい。

## 方法
`Pyreverse`を使うと以下のような図が出力できる（上クラス図，下パッケージ図）。
![classes_Pyreverse.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260124/94df5336-fb86-9d27-017c-8f0dfc2f34e3.png)
![packages_Pyreverse.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260124/094541ee-f38a-51d8-c8a2-d1f41be45219.png)

## 本記事の検証環境
- Windows10
- python3.7.3
- git-bash

## 環境構築手順
1. `python`と`pip`が使えることは前提とする。
1. `Pylint`をインストールする。`Pyreverse`が一緒にインストールされる。

   ```bash
   pip install pylint
   ```
1. 出力形式(dot, svg, png等)にかかわらず，`Graphviz`が必要なので，[ここ](https://graphviz.gitlab.io/download/)からインストーラを落としてインストールする。
1. `Graphviz`のbinフォルダ(筆者環境だと「C:\Program Files (x86)\Graphviz2.38\bin」)の[PATHを通す](https://qiita.com/sta/items/63e1048025d1830d12fd)。
1. 出力可能な形式を`dot -Txxx`で確認する。ここでエラーが起きると，Graphvizのインストールができてないか，パスが通っていない。

    ```sh
    $ dot -Txxx
    Format: "xxx" not recognized. Use one of: bmp canon cmap cmapx cmapx_np dot emf emfplus eps fig gd gd2 gif gv imap imap_np ismap jpe jpeg jpg metafile pdf pic plain plain-ext png pov ps ps2 svg svgz tif tiff tk vml vmlz vrml wbmp xdot xdot1.2 xdot1.4
    ```
1. インストールできたかテストする。

    ```sh
    pyreverse -o png -p Pyreverse pylint/pyreverse/
    ```
    以下の2つのファイル(冒頭の図)がカレントディレクトリの下にできていれば，インストール完了。
    - classes_Pyreverse.png
    - packages_Pyreverse.png

## 図の出力手順
```
pyreverse [option] <packages>
```
- \<packages>には変換したいパッケージ名を入れる。自作したpyファイルのファイル名・フォルダ名でもよい。フォルダを指定した場合は，そのサブフォルダ配下のすべてのpyファイルが解析される。ただし,`__init__.py`がないとエラーが起きる。
- pyreverseを実行することで，package diagramとclass diagramの2つが作成される。

以下，使いそうなオプションの説明。


- `-o, --output`オプションは変換する画像の拡張子を制御する（指定がないとdot形式になる）。出力可能な拡張子は`dot -Txxx`で確認できる。

    ```sh
    pyreverse -o png pylint/pyreverse/
    # 以下の二つのファイルが出力される。
    # classes.png
    # packages.png
    ```

- `-p, --project`オプションは変換後のファイル名の末尾を制御する。

    ```sh
    pyreverse -p TEST pylint/pyreverse/
    # 以下の二つのファイルが出力される。
    # classes_TEST.dot
    # packages_TEST.dot
    ```

- `-f, --filter-mode`オプションによって，出力する属性をフィルターできる。デフォルトだと，`PUB_ONLY`（non-publicな属性をすべて非表示）になっている。`ALL`にすると，すべて表示できる。ほかにも`SPECIAL`（コンストラクター以外の特殊メソッドをフィルターする），`OTHER`（protectedとprivateの属性をフィルターする）モードがある。

    ```sh
    pyreverse -o png -f ALL pylint/pyreverse/
    ```
- `-k, --only-classnames`オプションを指定すると，クラス図でクラス名のみを出力する（メソッドや属性を出力しない。クラスの依存関係だけ）。

    ```sh
    pyreverse -o png -k pylint/pyreverse/
    ```
    ![classes.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260124/71297ece-f67a-672f-a393-dabbb88b7957.png)


- `-m[yn], --module-names=[yn]`オプションを使用すると，クラス図内のクラス名にモジュール名を追加するかどうかを選択できる。デフォルトだと`-mn`（追加しない）。`-my`（追加する）を指定したときの出力を以下に示す。

    ```sh
    pyreverse -o png -k -my pylint/pyreverse/
    ```
    ![classes.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260124/49a27d0f-4662-58b9-6d5c-c24ed7556ac6.png)

- `-A, --all-ancestors`オプションはプロジェクト内のすべてのクラスの祖先を表示する。また`-S, --all-associated`オプションは関連するすべてのクラスを再帰的に表示する。自作パッケージを可視化するとき，この2つのオプションがないとスカスカなクラス図ができるので，毎回指定している気がする。

    ```sh
    pyreverse -o png -k -AS pylint/pyreverse/
    ```
    ![classes_AS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260124/e42a3be9-20b8-0280-eb20-455d147a5878.png)

- その他のオプションは`-h`でヘルプを参照。クラス図で描画するクラスを限定したり，親や子の深さを指定したりもできる。正直英語がわかりにくい気がする（上記の説明文も雰囲気で訳した）


## 最後に
- dot形式をご存じない方は，[ここ](https://qiita.com/kenichi-hamaguchi/items/568fabe2eb18daca02bc)など参照。
- 図中の緑色を変更する方法はなさそう。緑は見づらいorz。
- 使ってみてわかったが，クラス図はかなりクラス間の関係性に抜け漏れがある気がする。ダック・タイピングなpythonでは仕方のないことなのか。一応`-AS`オプションで足掻いてみることをお勧めする。
- 一方でパッケージ図はかなり安定している（当たり前だが）。import関係を可視化したいときは役に立つかも。
- pycharm だと自動でできるらしい。[参考](https://pleiades.io/help/pycharm/viewing-class-hierarchy-as-a-class-diagram.html)

## 参考にしたサイト
- [Pyreverse : UML Diagrams for Python](https://www.logilab.org/blogentry/6883) : 公式？
- [HarshaVardhanBabu/Instructions_UML_Python.rst](https://gist.github.com/HarshaVardhanBabu/9a47db9e33cf06e9e1e917520bb54056)
- [linuxBean14.04(49)pyreverseでPythonコードからUML図を生成](https://p--q.blogspot.com/2015/07/linuxbean140449pyreversepythonuml.html) : オプションの説明がかなり参考になります。
- [Python でパッケージやクラス間の依存関係を可視化してみた．[Pyreverse]](https://makutsueeken5.hatenablog.com/entry/2019/02/13/145049)