結果
-> PyPI 上位 100 パッケージでやってみた版: http://qiita.com/kitsuyui/items/a7cc56d476b59ff07f54
手元の機械学習用の virtualenv でこんな図を得た。一目瞭然。
numpy と scipy は同じくらい依存されてると思っていたけれど、
scipy の方はあまり依存されていない……。
多くのパッケージが six を使って Python 3 と Python 2 との互換性を持たせていることにメンテナの苦労が滲み出ている。
残りの項では pip freeze と pipdeptree 、 この図を出力したスクリプトを箇条書きにする。
pip freeze
$ 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 を使うと依存関係の階層をわかりやすく表示できる。
$ 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 を使用。
# 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)
使い方
$ pipdeptree --nowarn --local-only --freeze --all > dependencies.txt
$ python graphout.py < dependencies.txt
dependencies.gv と dependencies.gv.png を生成する。
余談
pip は真の依存関係の解決をしない らしい。
pip-tools (pip-review) や depsolver を使えば解決できるのかもしれない。