Python にインストールしたパッケージをグラフにしてみた

# 結果

-> PyPI 上位 100 パッケージでやってみた版: http://qiita.com/kitsuyui/items/a7cc56d476b59ff07f54

手元の機械学習用の virtualenv でこんな図を得た。一目瞭然。

![dependencies.gv.png](https://qiita-image-store.s3.amazonaws.com/0/25886/50b070e8-f6c5-e5df-c122-814deaf10b2a.png)

numpy と scipy は同じくらい依存されてると思っていたけれど、
scipy の方はあまり依存されていない……。

多くのパッケージが six を使って Python 3 と Python 2 との互換性を持たせていることにメンテナの苦労が滲み出ている。

残りの項では pip freeze と pipdeptree 、 この図を出力したスクリプトを箇条書きにする。

# pip freeze

```shell-session:
$ pip freeze -l
```

すると、現在の virtualenv 内のパッケージが表示できる。
しかし、階層はなく全てフラットに記載しているので、ずらっと出てくきて依存関係がわからない。

```
autoflake==0.6.6
autopep8==1.1.1
flake8==2.2.4
graphviz==0.4.3
hacking==0.10.1
isort==3.9.6
matplotlib==1.4.3
mccabe==0.2.1
natsort==3.5.6
nose==1.3.6
numpy==1.9.2
pandas==0.16.0
pbr==0.10.8
pep8==1.5.7
pies==2.6.3
pipdeptree==0.4.2
py==1.4.26
pyflakes==0.8.1
pyparsing==2.0.3
pytest==2.7.0
python-dateutil==2.4.2
pytz==2015.2
scipy==0.15.1
six==1.9.0
Theano==0.7.0
tox==1.9.2
virtualenv==12.1.1
```

# pipdeptree を使う

一方、 pipdeptree を使うと依存関係の階層をわかりやすく表示できる。

```shell-session:
$ pipdeptree --nowarn --local-only --freeze --all
```

```
autoflake==0.6.6
  - pyflakes==0.8.1
autopep8==1.1.1
  - pep8==1.5.7
flake8==2.2.4
  - pyflakes==0.8.1
  - pep8==1.5.7
  - mccabe==0.2.1
graphviz==0.4.3
hacking==0.10.1
  - pyflakes==0.8.1
  - six==1.9.0
  - flake8==2.2.4
    - pyflakes==0.8.1
    - pep8==1.5.7
    - mccabe==0.2.1
  - pep8==1.5.7
  - mccabe==0.2.1
  - pbr==0.10.8
    - pip
isort==3.9.6
  - pies==2.6.3
  - natsort==3.5.6
matplotlib==1.4.3
  - nose==1.3.6
  - python-dateutil==2.4.2
    - six==1.9.0
  - pyparsing==2.0.3
  - six==1.9.0
  - pytz==2015.2
  - numpy==1.9.2
mccabe==0.2.1
natsort==3.5.6
nose==1.3.6
numpy==1.9.2
pandas==0.16.0
  - pytz==2015.2
  - python-dateutil==2.4.2
    - six==1.9.0
  - numpy==1.9.2
pbr==0.10.8
  - pip
pep8==1.5.7
pies==2.6.3
py==1.4.26
pyflakes==0.8.1
pyparsing==2.0.3
pytest==2.7.0
  - py==1.4.26
python-dateutil==2.4.2
  - six==1.9.0
pytz==2015.2
scipy==0.15.1
six==1.9.0
Theano==0.7.0
  - numpy==1.9.2
  - scipy==0.15.1
tox==1.9.2
  - virtualenv==12.1.1
  - py==1.4.26
virtualenv==12.1.1
```

# グラフ化してみる。

graphviz を使うことにした。
以下は pipdeptree の出力を標準入力で渡し、 graphviz でグラフ化するスクリプトを書いた。
他の私の記事と同様、 Python 3 を使用。

```py3:graphout.py
# coding: utf-8
import re

import graphviz


def depfile_to_tree(file_like_object):
    line_pattern = re.compile(r'^(?:(?P<indent>(?:  )*)- )?'
                              r'(?P<pkg_name>.*?)'
                              r'(?:==(?P<version>.*?))?\n$')

    tree = []

    for line in file_like_object:
        matcher = line_pattern.match(line)
        indent, pkg_name, version = matcher.group('indent',
                                                  'pkg_name',
                                                  'version')

        if indent is None:
            hierarchy = 0
        else:
            hierarchy = int(len(indent) / 2)

        if hierarchy < len(tree):
            tree = tree[:hierarchy]

        tree.append((pkg_name, version))
        yield tuple(tree)


def main(pipdeptree_file):
    packages = set()
    dependencies = set()

    for package_tree in depfile_to_tree(pipdeptree_file):
        pkg_name = package_tree[-1][0]
        packages.add(pkg_name)
        if len(package_tree) < 2:
            continue
        parent, child = package_tree[-2:]
        dependencies.add((parent[0], child[0], child[1]))

    dot = graphviz.Digraph(comment='My Pip Dependencies')
    for p in packages:
        dot.node(p)

    for d in dependencies:
        dot.edge(*d)

    dot.engine = 'dot'
    dot.format = 'png'
    dot.render('dependencies.gv')

if __name__ == '__main__':
    import sys
    main(sys.stdin)
```

## 使い方

```shell-session:
$ pipdeptree --nowarn --local-only --freeze --all > dependencies.txt
$ python graphout.py < dependencies.txt
```

dependencies.gv と dependencies.gv.png を生成する。

# 余談

[pip は真の依存関係の解決をしない](https://github.com/pypa/pip/issues/988) らしい。
[pip-tools](https://github.com/nvie/pip-tools) (pip-review) や [depsolver](https://github.com/enthought/depsolver) を使えば解決できるのかもしれない。