Lostman

迷いがちな日々のこととかを

Python の module search path ついて調べてみる

virtualenv を導入しよう思い立ったのですが、 Python のパス周りについての理解が浅くて何をしているか分からなかったので調査してみました。

目標

(virtualenv を理解する前提として) Python が module search path をどうやって設定しているか理解する。

※モジュールのインストールについてはまたそのうち。

調査にあたってはこちらのブログを参考にさせて頂きました。 なお以下は自分のマシン(Ubuntu)を前提に書いています。

Python がモジュールをロードする仕組み

import spam と書いてある場合、 Python

  1. まず built-in module の中に spam があるかどうか探し
  2. 次に sys.path に書いてあるパスの下に spam がないかを先頭から順番に検索

してくれます。 詳しいことは公式ドキュメント(6.1.2. The Module Search Path)を読んで下さい。短いので。

sys.path がどうやって作られているか把握できればモジュール切り替えの仕組みが分かりそうな気がしてきました。

何も変更を加えていなければ sys.path は以下のような感じになるはずです^1

>>> import sys
>>> sys.path
['', '/usr/lib/python2.7',
 '/usr/local/lib/python2.7/dist-packages',
 '/usr/lib/python2.7/dist-packages']

先頭の '' はカレントディレクトリを指します。 これで、うっかりテスト用に built-in module と同じ名前のモジュールを作ってしまうと、動かなくなる理由が分かります。

では、残りは一体誰がいつ設定してくれているのでしょうか?


※インポートしたモジュールがどこから来ているかは、

>>> import MySQLdb
>>> MySQLdb.__file__
'/usr/lib/python2.7/dist-packages/MySQLdb/__init__.pyc'

で調べることができます。

sys.path を初めに設定しているのは誰だ?

Python は起動後に lib/pythonx.x を探して、その下にある site.py をロードすることで sys.path を設定しているそうです。

  1. 起動後に見に行く lib/pythonx.x のパス
  2. その後 site.py によって設定されるパス

を把握しておけば、このあたりのだいたいの挙動がつかめそうですね。

以下はvirtualenv の中の人の解説を見るほうが詳しく分かると思うので、時間のある方はそちらを。

どこの lib/pythonx.x を見にいくのか?

実行バイナリのパスを起点として上にさかのぼり lib/pythonx.x/os.py が見つかったらそれを使うようです。 (x.x には python のバージョン番号上二桁が入ります)

言葉で説明してもうまく伝わりませんが、もしディレクトリ構成が以下のようになっている場合、

/usr
    /lib
        /python2.7/os.py
    /local
        /bin/python2.7
  1. /usr/local/bin/python2.7 を実行すると
  2. /usr/local/lib/python2.7/os.py を探しに行くが見つからないので
  3. 一つ上の /usr/lib/python2.7/os.py を探しにいってめでたく発見

となります。

一番上まで行っても見つからない場合はコンパイル時に --prefix オプションで設定されたパスが使われるようです^2

どこの lib/pythonx.x が使われたかは sys.prefix で確認することができます^3。 (lib/pythonx.x を除いたものが入ります)

これでめでたく site.py をロードすることができます。

site.py で設定されるパス

site.py がロードされると、main 関数が呼ばれ、以下の順番で sys.path にパスが追加されていきます。
(append していくため、最初に追加するものが先頭にきます=優先順位が高いです)

  1. USER_SITE

    USER_SITE は以下のコードで返ってくるパスです。 自分のマシンだと $HOME/.local/lib/pythonx.x/site-packages になります。

    ```python from sysconfig import get_path import os

    USER_SITE = get_path('purelib', '%s_user' % os.name) ```

  2. USER_BASE/local/lib/pythonx.x/dist-packages

    USER_BASE は以下のコードで返ってくるパスです。 自分のマシンだと $HOME/.local になります。

    ```python from sysconfig import get_config_var

    USER_BASE = get_config_var('userbase') ```

  3. USER_BASE/lib/pythonx.x/dist-packages

  4. PREFIX/local/lib/site-packages or PREFIX/local/lib/dist-packages

    PREFIX には sys.prefix または sys.exec_prefix が入ります。

    lib 以下が site-packges になるか dist-packages になるかは OS によって変わってきます^4

  5. PREFIX/lib/site-packages or PREFIX/lib/dist-packages

またパスを追加すると同時に、ディレクトリ中の *.pth をロードして sys.path に追加します。

site.py の中では他にも usercustomize, sitecustomize というパッケージがロードされます。これらのモジュールを $HOME/.local 下に配置すれば、他のパッケージがインポートされる前に色々な設定を行うことができます。活用法についてはusercustomize による Python カスタマイズが詳しいです。

結論

  1. バイナリのパスを調べる
  2. そこから遡っていって lib/pythonx.x/os.py のある場所を探す
  3. site.py で何が sys.path に追加されているかチェック

することで module search path がどうやって設定されているかを理解することができました。