PYTHONPATHの挙動を正確に把握する

ASP, さくらインターネット, Python

08:56:03, by admin Email , 546 words, 7392 views   Japanese (JP) del.icio.us

ちなみに本文は長いので。暇な人だけ読むと良い。

(ノ・・)ン。。。。。。(((●コロコロッ

Python使うとハマる原因になりそうなもの、
PYTHONPATH

PHPでいうとinclude_pathと同じで、
パスが通らない問題が発生する。

PYTHONPATHとは、環境変数として定義して置くことで、
内部利用してくれるPATHを指定してくれるもの。
しかし、このPYTHONPATHの挙動を正確に理解している人は、
熟練のPython使いでも少ない気がする。
それとも昔のPythonとでは挙動が違うのかもしれない。
私が調べた時の環境はさくらインターネットのPython2.4。

きっかけはさくらインターネットでCGI利用した時のこと。
コマンドラインで動作するのを確認してから、
CGIで実行しようとした時にパスが通ってない問題が発生した。
.htaccessにSetEnvディレクティブでPYTHONPATHを定義するのが定石の様だが、
さくらインターネットはSetEnvディレクティブの定義を全部認めてくれないらしく、
PYTHONPATHは見事にスルーされていた。
(cgitbを知る前だったから原因がわかるまで異常に時間がかかったのは余談です)

対策を施そうといろいろ調べたが、
SetEnvで設定すればいいよとしか書いてない。
代替案を提示してくれている以下のサイトを発見し、
「さくらのレンタルサーバ」で Python 外部モジュールを使う
さっそく試してみたが、
全部ダメだった。 ...orz
(アイデアその2は論外)
二年以上前の記事だから仕方がないか。


調査結果を先に言うと
正確には
  • Python起動後import site時に読み込まれる
  • PYTHONPATHで指定されたディレクトリとそのディレクトリ下にある*.pthに登録されているサブディレクトリをsys.pathリストに登録する
  • コンポーネントを呼び出す時、システムは環境変数PYTHONPATHを見ていない
らしい。

また、OSに依るのかもしれないが、
  • インタプリタ行でPYTHONPATHは指定出来ることは少ない
様で、
今のところうまくいった試しがない。
Linux, Freebsd両方ダメだった。

私なりの結論としては、
さくらインターネットなどSetEnvディレクティブが使えないサーバや、
いろんな環境を想定しなきゃならない配布用アプリケーションでは、
でPYTHONPATHを(擬似的に)指定するには、
#!/usr/bin/env python
site.addsitedir('/home/xxx/local/lib/python2.4/site-packages')
# main content
という感じで行こうと思う。
site.addsitepackages()という線もあるかな。
(実際は、前回の記事を踏まえているが)


PYTHON起動時に利用されるとか、
sys.path.append()と等価だとか、
putenvでPYTHONPATHを登録すればいいなどの、
誤情報に散々惑わされてヘトヘト。



[More:]


以下ではPYTHONPATHを環境変数に登録してあることを前提にしている。
登録内容は
PYTHONPATH=/home/xxx/local/lib/python2.4/site-packages
適当なコンポーネントをインストールしている。

●Python起動後import site時に読み込まれる
Pythonはコマンドライン引数に-Sを付けるとimport siteを省いた状態で起動することが出来る。
これは以前の記事setdefaultencodingの謎で書いた。
-Sオプション付きで起動しsys.pathの情報を確認する。
その後siteをimportしsys.pathの情報をもう一度確認する。
$ python -S
>>> import sys; print sys.path
['', '.', '/home/xxx/local/lib/python2.4/site-packages', '/usr/local/lib/python24.zip', '/usr/local/lib/python2.4/', '/usr/local/lib/python2.4/plat-freebsd6', '/usr/local/lib/python2.4/lib-tk', '/usr/local/lib/python2.4/lib-dynload']
>>> import site; print sys.path
['/home/xxx/local/lib/python2.4/site-packages/setuptools-0.6c7-py2.4.egg', ・・・, '/home/xxx/local/lib/python2.4/site-packages/wsgiref-0.1.2-py2.4.egg', '/home/xxx/local/lib/python2.4/site-packages', '/usr/local/lib/python24.zip', '/usr/local/lib/python2.4', '/usr/local/lib/python2.4/plat-freebsd6', '/usr/local/lib/python2.4/lib-tk', '/usr/local/lib/python2.4/lib-dynload', '/usr/local/lib/python2.4/site-packages']
となり、
import siteする前と後ではsys.pathの内容が異なっているのが解る。
つまりPYTYONPATHはシステム内部で動作しているものではなく、
siteコンポーネントを経由して(つまりスクリプト上)呼び出せれていることがわかる。

●PYTHONPATHで指定されたディレクトリとそのディレクトリ下にある*.pthに登録されているサブディレクトリをsys.pathリストに登録する
PYTHONPATHで指定しているディレクトリにdummy.pthというファイルと、
dummy.eggというディレクトリを作り、
それが呼び出されるかどうかを確認する。
$ mkdir ~/local/lib/python2.4/site-packages/dummy.egg
$ cat > ~/local/lib/python2.4/site-packages/dummy.pth
./dummy.egg
$ python
>>> import sys; sys.path
['', '/home/dozo/local/lib/python2.4/site-packages/setuptools-0.6c7-py2.4.egg', ・・・, '/home/dozo/local/lib/python2.4/site-packages', '/home/dozo/local/lib/python2.4/site-packages/dummy.egg', '/usr/local/lib/python24.zip', '/usr/local/lib/python2.4', '/usr/local/lib/python2.4/plat-freebsd6', '/usr/local/lib/python2.4/lib-tk', '/usr/local/lib/python2.4/lib-dynload', '/usr/local/lib/python2.4/site-packages']
dummy.eggというディレクトリが追加されていることが解る。
省略しているが拡張子をtxtなど変えると追加されなくなる。


では、sys.path.appendで追加した場合はどうか?
-Sオプションを付けてPYTHONを起動する。
その後sys.path.appendでPYTHONPATHに登録しているパスを追加してみる。
$ python -S
>>> import sys; sys.path
['', '.', '/home/xxx/local/lib/python2.4/site-packages', '/usr/local/lib/python24.zip', '/usr/local/lib/python2.4/', '/usr/local/lib/python2.4/plat-freebsd6', '/usr/local/lib/python2.4/lib-tk', '/usr/local/lib/python2.4/lib-dynload']
>>> sys.path.append('/home/xxx/local/lib/python2.4/site-packages'); print sys.path
['', '.', '/home/xxx/local/lib/python2.4/site-packages', '/usr/local/lib/python24.zip', '/usr/local/lib/python2.4/', '/usr/local/lib/python2.4/plat-freebsd6', '/usr/local/lib/python2.4/lib-tk', '/usr/local/lib/python2.4/lib-dynload', '/home/xxx/local/lib/python2.4/site-packages']
一つ増えただけでサブディレクトリは登録されていないのが解るだろうか?
('/home/xxx/local/lib/python2.4/site-packages'がダブっているのはinstall_dirを設定しているため)

間違って認識している人が多いようで、
国内海外問わずsys.path.appendで提案する人が多かった。
どちらかというとsite.addsitedirの方が近い動作をする。


●コンポーネントを呼び出す時、システムはPYTHONPATHを見ていない
もしimport時にPYTHONPATHを見ているのだとすると、
-Sオプションを付けてもキチンと呼び出されるはずである。
setuptoolsというコンポーネントがインストールされているので、
それを呼び出してみることにする。

$ python -S
>>> import os; print os.environ['PYTHONPATH']
/home/xxx/local/lib/python2.4/site-packages
>>> import setuptools
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ImportError: No module named setuptools
>>> import site; import setuptools
>>>

environにはPYTHONPATHがキチンと定義されているが、
setuptoolsを呼び出してもエラーが出ている。
しかし、import siteをした後、つまりsys.pathに展開された後では、
キチンとimportしエラーが出なくなっているのが解る。

つまりコンポーネントを呼び出す時システムはPYTHONPATHを見ておらず、
展開されたsys.pathだけを見ていると言うことになる。

●インタプリタ行でPYTHONPATHは指定出来ることは少ない
これは成功した試しがないので、
憶測の域を出ないのだが、
プログラムの先頭に
#!/usr/bin/env PYTHONPATH=/home/xxx/local/lib/python2.4/site-packages python
と記述することで、
PYTHONPATHを環境変数を設定出来るという記述がいくつか見つかった。

一番すっきりした方法でいいのだが、
OS環境なのかenvのバージョンなのか。
環境に大きく左右されるっぽい。
実際やってみると入力待ち状態で止まってしまう。
改行コードの問題でNot Foundとかになるならまだすっきりするが、
はて・・・。

なので、この方法で使うことは避ける方が良いだろう。




まとめると、
  • Python起動後import site時に読み込まれる
  • PYTHONPATHで指定されたディレクトリとそのディレクトリ下にある*.pthに登録されているサブディレクトリをsys.pathリストに登録する(sys.path.append()と等価ではない)
  • コンポーネントを呼び出す時、システムはPYTHONPATHを見ていない
ということだ。
PYTHONをCGIで利用する、
または配布用のアプリケーションを利用する際は、
この辺りを考慮しておく必要がありそうだ。


・・・そういえば過去tracが動かせなかった理由は、
ひょっとするとこれかな。。。




関連リンク:

さくらインターネット



Trackback address for this post:

http://hain.jp/htsrv/trackback.php/189

Comments, Trackbacks:

Comment from: M.Shibata [Visitor] · http://www.emptypage.jp/
こんにちは。「「さくらのレンタルサーバで」Python 外部モジュールを使う」を書いた当人です。

どうも環境変数 PYTHONPATH のほうを主と考えられているようですが、そのせいで少し話を難しくさせてしまっているように感じました。

Python がモジュールを探すのは sys.path からです(ドキュメントにある通り)。PYTHONPATH はその sys.path に値を追加するための手段のひとつです。あくまで主役は sys.path のほうです。PYTHONPATH は、*.pth を見たりして、それ自身の流儀で sys.path にパスを追加しているわけです。

だから PYTHONPATH=/foo/bar が sys.path.append('/foo/bar') と等価でないというのは、またたしかにそうなのですが、それは PYTHONPATH の仕組みのほうが /foo/bar から先をいろいろ読み取って sys.path.append(...) しているからという話であって、同じ引数を与えて結果が等価であるかどうかなんてのはあまり重要ではありません。/foo/bar に置いたモジュールをインポートしたいのであれば sys.path.append('/foo/bar') とすればいいわけですし、ほかにも追加するパスがあるのなら sys.path.append を続けてずらずら並べればいいのです。

「さくらのレンタルサーバ」のサービスに限っていえば、あそこで書いてあるノウハウはまだ時代遅れになってないはずですよ(さくらのほかのホスティングサービスまではわかりませんが)。実際、僕はそこで Python で書いた CGI を変わらず動かし続けてますし。

スクリプト先頭の「#!/usr/bin/env PYTHONPATH=/foo/bar python」については、動くはずなのですが、としか言いようがないのですが、可能性としては記事内で疑問とされていた通り、改行コードの問題が考えられるでしょう。もし、スクリプトの改行コードが CR+LF (Windows スタイル) の場合、改行コードが LF のみの環境 (FreeBSD) だと #! で実行されるのが「#!/usr/bin/env PYTHONPATH=/foo/bar python (CR) script.py」になってしまいます。このため後ろの script.py の部分が渡らずに、Python が対話モードで起動してしまうのでしょう。

結局、あるモジュールをインポートしたければそこへのパスを sys.path に追加する、というだけのことで、そんなに難しい話ではないはずです。わざわざ PYTHONPATH の挙動を再現したりする必要も普通はないでしょう。sys.path にモジュールへのパスを追加、でうまくいかなかったのだとすれば、それはまたなにか別の原因があったのだと思いますよ。

site モジュールについてはよく知らなかったので、勉強になりました。
長々と失礼しました、ご参考になれば幸いです。それでは。
PermalinkPermalink 2008/01/24 @ 01:45
Comment from: admin [Member]
仰りたいことはよくわかります。
根本的に異なる点はPYTHONPATHの重要視の度合いでしょう。
私はPYTHONPATHは最重要パラメータの一つだと考えております。
理由はシステムは「パスがずれる」というくだらない理由で簡単に動かなり、
その設定項目は設定の第一条件となるからです。
しかも、それを設定する項目には細心の注意を払う必要があります。
PHPではinclude_pathというパラメータがありますが、
これは付け加えるパスの順番まで注意しなければならないものです。

そういうこともあってPYTHONPATHを取り上げて記事にしております。
このPYTHONPATHがシステムにとってどのような意味合いを持つのかを考察したものです。
基準がPYTHONPATHになるのはご容赦ください。


sys.path.appendですが、
これをモジュールが必要になる度に、
いくつも書くのは良くない方法だと考えております。
理由は調整が非常に困難になるからです。
サンプリングの段階ではそう言う風に使うこともありますが、
構築の段階ではどこか一つにまとめるのが流れです。
ならば、一度にすべて組み込める仕組み(それがPYTHONPATH)の方が重要です。
システム規模が大きくなればなるほど顕著になるはずです。


スクリプト先頭の記述ですが、
うまくいかなかった理由は今でも不明です。
ですが、何故うまくいかないのかを考えるより、
うまくいく他の選択肢を使う方が効率が良いと考えております。
なので、省く方が賢明だと判断しました。



PATHはどのプログラミング言語でもナーバスでハマリの原因になりやすいものです。
私が欲しているものは、環境によって動いたり動かなかったりするものではなく、
確実に動作する方法です。(セオリーに沿っているかは無関係)
私の方法なら確実に動作しますし、
スクリプトの先頭付近の設定以外は操作する必要はありません。
(実際はコンフィグファイルとして別にまとめると思います。)
道筋から少しでもはずれるものは採用しない方向で記事にしております。
お気を悪くされたのでしたら申し訳ございます。
コメントありがとうございました。
PermalinkPermalink 2008/01/30 @ 17:48

Leave a comment:

Your email address will not be displayed on this site.
Your URL will be displayed.

Allowed XHTML tags: <p, ul, ol, li, dl, dt, dd, address, blockquote, ins, del, span, bdo, br, em, strong, dfn, code, samp, kdb, var, cite, abbr, acronym, q, sub, sup, tt, i, b, big, small>
(Line breaks become <br />)
(Set cookies for name, email and url)
(Allow users to contact you through a message form (your email will NOT be displayed.))
This is a captcha-picture. It is used to prevent mass-access by robots.

Please enter the characters from the image above. (case insensitive)

powered by b2evolution

shinobi

Neighbors
Relative
Favorites
PR

極論istの技術屋を始めて早幾年。 流れの速い業界の波にもまれながらも精一杯生きている様をとくとごらんあれ。

Archives
スポンサー

Latest bookmark
Search

Categories

Who's Online?
Misc
Syndicate this blog XML

Valid XHTML 1.0! Valid CSS! Valid RSS 2.0! Valid Atom 1.0!