[Python] os.pathモジュールが使えるようになるまで

環境

この記事の内容は、Ubuntu Linux 6.10, Python 2.4.4c1で確認しました。

疑問点

例えば、以下のようなファイル構造があったとします。

.
`-- foo
    |-- __init__.py
    `-- bar.py

このうち、foo/__init__.pyは空です。foo/bar.pyは、以下のようになっています。

def baz():
    print "baz"

このとき、fooパッケージをimportしただけでは、barモジュールにはアクセスできません。barモジュールにアクセス(し、その中の関数を使用)するためには、foo.barをimportする必要があります。

>>> import foo
>>> foo.bar
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: 'module' object has no attribute 'bar'
>>> import foo.bar
>>> foo.bar
<module 'foo.bar' from 'foo/bar.pyc'>
>>> foo.bar.baz()
baz

ところが、os.pathモジュールは、osモジュールをimportした時点で使用できるようになります。もちろん、os.pathモジュール内の関数も使用可能になります。

>>> import os
>>> os.path.curdir
'.'

これを疑問に思ったので、調べてみました。

結論

まず、osはパッケージではなく、モジュールです。
os.pathモジュールは、osモジュールをimportしたとき、sys.modulesに追加されます。

詳細

当初、osはパッケージだと思っていたのですが、調べてみたらモジュールでした。

$ ll /usr/lib/python2.4/os.py
-rw-r--r-- 1 root root 24258 2006-10-12 06:51 /usr/lib/python2.4/os.py

つまり、

import os.path

と記述すると、os/path.pyが読み込まれると思っていたのですが、これは間違いのようです。

ではos.pathモジュールはどこでシステムに追加されているかというと、os.pyの中でした。os.pyの中から、関連している箇所だけを抜き出してみます。

_names = sys.builtin_module_names

if 'posix' in _names:
    import posixpath as path
elif 'nt' in _names:
    import ntpath as path
elif 'os2' in _names:
    if sys.version.find('EMX GCC') == -1:
        import ntpath as path
    else:
        import os2emxpath as path
elif 'mac' in _names:
    import macpath as path
elif 'ce' in _names:
    import ntpath as path
elif 'riscos' in _names:
    import riscospath as path
else:
    raise ImportError, 'no os specific module found'

sys.modules['os.path'] = path

すなわち、sys.builtin_module_namesからプラットフォームを判定し、それに応じたモジュールを読み込んで、os.pathモジュールとしているわけです。実際、os.pathを対話プロンプトで表示させると、

>>> import os.path
>>> os.path
<module 'posixpath' from '/usr/lib/python2.4/posixpath.pyc'>

となり、os/path.pyというファイルから読み込まれているのではないことが分かります。

蛇足ですが、os.pathモジュールとして読み込まれている個々のモジュールの挙動を比較してみました。

>>> import posixpath
>>> posixpath.join("foo", "bar")
'foo/bar'
>>> import ntpath
>>> ntpath.join("foo", "bar")
'foo\\bar'
>>> import os2emxpath
>>> os2emxpath.join("foo", "bar")
'foo/bar'
>>> import macpath
>>> macpath.join("foo", "bar")
':foo:bar'
>>> import riscospath
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ImportError: No module named riscospath

私の環境では、riscospathモジュールは、インストールもされないようです。