なんでこれを今まで考えてこなかったのかちょっと不思議。
wheel の方がベターなのでわ、てのは今は無視。古いテクニックの方が適切な場合もある。想定しているのは「標準 Python 本体しかインストールされていないレンタルサーバ」で自作 cgi を python で作る際の依存モジュールをどうするか問題、とか、あるいは kivyLauncher の「パッケージマネージャがないので」問題、への一つの回答として。
なんで egg にしたいか、というのはだからつまり、「アップロード」の手間の話。ロリポップ! レンタルサーバ相手に(Python で)FTPSや笑えないモジュールインポートのEOFError (Python)なんかで苦労して見せた通り、「大量のファイル」のコピーや移動はとかくトラブルメーカなのだ。これはリモートに限らない。いつでも「一個のデカいファイル」より「大量の小さなファイル」の方が取り扱いが大変だ、一定サイズ(2GB、4GB などのある種の制約)を超えてない限りは。無論、複雑なパッケージツリーをミラーする、という行為そのものも普通は「ダルい」。どんなにクレバーな手(例えば rsync)を使おうが。
egg の作り方は、「setup.py
を setuptools
で書いているプロジェクト」であればこれだけ:
1 [me@host: ~]$ cd somelib
2 [me@host: somelib]$ python setup.py bdist_egg
これにより dist
フォルダに egg
が作られる。無論 setuptools
は標準添付ライブラリではないので、インストールしていないならインストールする必要がある。
「setup.py
を setuptools
で書いていないプロジェクト」は少々改変する必要がある。実例はまぁなんでもいいのだが、小さそうなものとして dictlib を探してみた。こうする:
1 #from distutils.core import setup # NOT distutils
2 from setuptools import setup # USE setuptools
3 setup(
4 name = 'dictlib',
5 packages = ['dictlib'],
6 version = '1.0.3',
7 description = 'Yet another Dictionary Library including good deep merge and dictionary as objects',
8 author = 'Brandon Gillespie',
9 author_email = 'bjg-pypi@cold.org',
10 url = 'https://github.com/srevenant/dictlib',
11 download_url = 'https://github.com/srevenant/dictlib/tarball/1.0',
12 keywords = ['dict', 'union', 'object'],
13 classifiers = [],
14 )
これで python setup.py bdist_egg
を実行出来る。
そもそも setup.py
がないもの、これはおそらく「あなたの自作モノ」だろう。この場合はミニマルなこんな:
1 from setuptools import setup, find_packages
2
3 setup(
4 name = "yourpylib",
5 version = "0.1",
6 packages = find_packages()
7 )
でええよ、と Python 101: easy_install or how to create eggs で紹介されてた。自分では試してない。
あとはこの egg
をどうやって使えるのか、ということだが、egg (つまり zip ファイル) に固めてないものと少しだけ扱いが違う。以下で伝わる?
1 [me@host: ~]$ ls *.egg
2 dictlib-1.0.3-py2.7.egg
3 [me@host: ~]$ /usr/local/bin/python2.7 -m pydoc dictlib | head -4
4 no Python documentation found for 'dictlib'
5
6 [me@host: ~]$ PYTHONPATH=dictlib-1.0.3-py2.7.egg /usr/local/bin/python2.7 -m pydoc dictlib | head -4
7 Help on package dictlib:
8
9 NAME
10 dictlib - Dictionary Utility Library
つまり egg (zip) で固めちゃったものはフラットに置いてある *.py
や *.pyc
と違う。java の classpath の振る舞いを知ってる人なら jar の類推からの理解そのままでいい。egg (zip) は「ディレクトリ」と同じ扱いがなされる。(なおこのシカケそのものは setuptools
非依存なので、配備先に setuptools
がいるかどうかは気にしなくていい。)
というわけで「egg を置くだけ」というわけにもいかなくて、このパッケージ検索パス問題も一緒に考えなければならない、ということなのだけれど、比較的手軽なのは pth ファイルを作って配置して、sitedir をゴニョゴニョすることかな、と思う。pth ファイルは例えばこんな風に作っちゃえばいい:
1 [me@host: ~]$ ls *.egg
2 dictlib-1.0.3-py2.7.egg
3 [me@host: ~]$ echo ./dictlib-1.0.3-py2.7.egg > dictlib-1.0.3.pth
pth ファイルは「置くだけ」では(標準の site-packages などに置かない限りは)ダメで、「pth ファイルたち置き場」をインタプリタに教えてあげる必要がある:
1 >>> import dictlib
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 ImportError: No module named dictlib
5 >>> import site
6 >>> site.addsitedir(".")
7 >>> import dictlib
結果としてはいくつか悩ましいことはある:
- pth ファイルを egg 一個につき一つ作るか、一つに代表させてまとめちゃうか問題
- そもそも egg をどこに置こうか問題
site.addsitedir(".")
を毎度やるのもなぁ問題
1. については一長一短だと思う。特にリモートに配置する場合は、一個一個作る方が「ダウンロードしてきたものに追記してアップロード」としなくていいので簡単な気がする一方で、当然ファイルが多くなってくると管理しにくくもなるわけで。
2.、3. については、「user site」を受け容れるつもりならそこに置けばいい。(python -m site --user-site
で場所はわかる。)ただ、特に「レンタルサーバ」での扱いの場合、さらに特に「cgi としてや cron で動くスクリプト」の場合その「「user site」を受け容れる」こと自体が苦痛になりうるので、「カレントに置いて site.addsitedir(".")
」が、残念ながら一番簡単なんじゃないかなと思う。
08:30追記:
すまん、ちょっとだけ言葉足らずだった。これ、「インストール場所を変える」で済む場合はそうしてね。そうしたくない理由はこれは…、これこそが「パッケージマネージャ」の宿命。つまり「依存物を勝手にインストールしようと、ネットワークアクセスする」ことから逃げたいから、でしょ? 業務のためのサーバがインターネットから隔離されてるなんてことは滅茶苦茶多いわけで。てことね。(まぁ kivyLauncher も例にしたんで、わかる人はわかるとは思うけれど。)