pkgutil – パッケージユーティリティ

目的:モジュール検索パスに特定パッケージを追加して、パッケージのリソースを扱う
利用できるバージョン:2.3 以上

pkgutil モジュールは、Python パッケージを扱う機能を提供します。 extend_path() はパッケージのサブモジュールのインポートパスを変更し、 get_data() はパッケージとして配布されたファイルリソースへのアクセスを提供します。

パッケージのインポートパス

extend_path() 関数は、 sys.path の他のディレクトリを追加するために、任意のパッケージの検索パスを変更するのに使用されます。これは、パッケージの開発バージョンでインストール済みバージョンを上書きしたり、1つのパッケージの名前空間内に共有モジュールやプラットフォーム固有のものを組み合わせたりするのに使用されます。

extend_path() を呼び出す最も一般的な方法は、次の2行をパッケージの __init__.py へ追加します。

import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)

extend_path() は、第二引数で渡された任意のパッケージのサブディレクトリ名を含むディレクトリのために sys.path を調べます。ディレクトリのリストは、第一引数で渡されたパスと組み合わせて、パッケージのインポートパスに利用できる1つのリストを返します。

demopkg というサンプルパッケージは、次のファイルを含みます。

$ find demopkg1 -name '*.py'
demopkg1/__init__.py
demopkg1/shared.py

demopkg1/__init__.py は次の通りです。

import pkgutil
import pprint

print 'demopkg1.__path__ before:'
pprint.pprint(__path__)
print

__path__ = pkgutil.extend_path(__path__, __name__)

print 'demopkg1.__path__ after:'
pprint.pprint(__path__)
print

変更による違いを分かりやすくするために print 文で変更前後の検索パスを表示します。

demopkg の拡張機能を含む extension ディレクトリは次の通りです。

$ find extension -name '*.py'
extension/__init__.py
extension/demopkg1/__init__.py
extension/demopkg1/not_shared.py

簡単なテストプログラムは demopkg1 パッケージをインポートします。

import demopkg1
print 'demopkg1           :', demopkg1.__file__

try:
    import demopkg1.shared
except Exception, err:
    print 'demopkg1.shared    : Not found (%s)' % err
else:
    print 'demopkg1.shared    :', demopkg1.shared.__file__

try:
    import demopkg1.not_shared
except Exception, err:
    print 'demopkg1.not_shared: Not found (%s)' % err
else:
    print 'demopkg1.not_shared:', demopkg1.not_shared.__file__

このテストプログラムをコマンドラインから直接実行すると、 not_shared モジュールが見つかりません。

Note

この記事のサンプルのファイルシステム上のフルパスは、変更された部分を強調するために短縮しています。

$ python pkgutil_extend_path.py

demopkg1.__path__ before:
['.../PyMOTW/pkgutil/demopkg1']

demopkg1.__path__ after:
['.../PyMOTW/pkgutil/demopkg1']

demopkg1           : .../PyMOTW/pkgutil/demopkg1/__init__.py
demopkg1.shared    : .../PyMOTW/pkgutil/demopkg1/shared.py
demopkg1.not_shared: Not found (No module named not_shared)

但し、この extension ディレクトリを PYTHONPATH に追加して、同じプログラムを再実行すると違う結果になります。

$ export PYTHONPATH=extension
$ python pkgutil_extend_path.py
demopkg1.__path__ before:
['.../PyMOTW/pkgutil/demopkg1']

demopkg1.__path__ after:
['.../PyMOTW/pkgutil/demopkg1',
 '.../PyMOTW/pkgutil/extension/demopkg1']

demopkg1           : .../PyMOTW/pkgutil/demopkg1/__init__.pyc
demopkg1.shared    : .../PyMOTW/pkgutil/demopkg1/shared.pyc
demopkg1.not_shared: .../PyMOTW/pkgutil/extension/demopkg1/not_shared.py

extension ディレクトリ内の demopkg1 が検索パスへ追加されたので、 not_shared モジュールがそこで見つかります。

この方法でパスを拡張することは、共通パッケージとプラットフォーム固有のモジュールを組み合わせるのに都合が良く、特に C 言語の拡張モジュールを含むプラットフォーム固有のモジュールに便利です。

パッケージの開発バージョン

あるプロジェクトで拡張機能を開発する一方で、インストール済みパッケージへの変更をテストしなければならないことがよくあります。インストール済みのパッケージを開発バージョンと置き換えることは、悪い考えかもしれません。それは必ずしも正しいとは言えなくて、システム上のその他のツールがインストール済みパッケージに依存している可能性があるからです。

完全に独立したパッケージのコピーをもつ開発環境は virtualenv で設定できますが、些細な変更のために全ての依存関係をもつ仮想環境を構築するのは、やり過ぎかもしれません。

別の方法としては、開発環境でパッケージがもつモジュールの検索パスを変更するのに pkgutil を使用することです。このケースでは、但し、そのパスは置き換えなければならないので、開発バージョンはインストール済みバージョンを上書きします。

任意のパッケージ demopkg2 は次のファイルを含みます。

$ find demopkg2 -name '*.py'
demopkg2/__init__.py
demopkg2/overloaded.py

demopkg2/overloaded.py に置かれた開発中の関数と一緒に、インストール済みバージョンは、

def func():
    print 'This is the installed version of func().'

demopkg2/__init__.py

import pkgutil

__path__ = pkgutil.extend_path(__path__, __name__)
__path__.reverse()

を含みます。

reverse() は、 pkgutil が検索パスへ任意のディレクトリを追加するのを保証するために使用され、デフォルトの位置よりも 先に インポートのために調べられます。

このプログラムは、 demopkg2.overloaded をインポートして func() を呼び出します。

import demopkg2
print 'demopkg2           :', demopkg2.__file__

import demopkg2.overloaded
print 'demopkg2.overloaded:', demopkg2.overloaded.__file__

print
demopkg2.overloaded.func()

特別なパス設定を行わずに、そのテストプログラムを実行すると、インストール済みの func() を呼び出して表示します。

$ python pkgutil_devel.py
demopkg2           : .../PyMOTW/pkgutil/demopkg2/__init__.py
demopkg2.overloaded: .../PyMOTW/pkgutil/demopkg2/overloaded.py

開発ディレクトリは、次のファイルを含みます。

$ find develop -name '*.py'
develop/demopkg2/__init__.py
develop/demopkg2/overloaded.py

そして overloaded の変更されたバージョンは次になります。

def func():
    print 'This is the development version of func().'

テストプログラムが検索パスの develop ディレクトリで実行されるときに読み込まれます。

$ export PYTHONPATH=develop
$ python pkgutil_devel.py

demopkg2           : .../PyMOTW/pkgutil/demopkg2/__init__.pyc
demopkg2.overloaded: .../PyMOTW/pkgutil/develop/demopkg2/overloaded.pyc

PKG ファイルでパスを管理する

前節の最初のサンプルは、 PYTHONPATH にディレクトリを追加して、検索パスを拡張する方法を説明しました。その他にディレクトリ名を含む *.pkg ファイルを利用して検索パスを追加することもできます。PKG ファイルは、 site モジュールが利用する PTH ファイルとよく似ています。これらのファイルは、パッケージの検索パスを追加するために、1行につき1ディレクトリ名を含みます。

最初のサンプルからアプリケーションのプラットフォーム固有の部分を構成する別の方法は、各オペレーティングシステム用に別ディレクトリを使用して、検索パスを拡張する .pkg ファイルを含めます。

このサンプルでは、同じ demopkg1 ファイルを使用して、次のファイルも含めます。

$ find os_* -type f
os_one/demopkg1/__init__.py
os_one/demopkg1/not_shared.py
os_one/demopkg1.pkg
os_two/demopkg1/__init__.py
os_two/demopkg1/not_shared.py
os_two/demopkg1.pkg

PKG ファイルは、拡張されるパッケージ名と一致するように demopkg1.pkg というファイル名にします。両方とも次の内容を含みます。

demopkg

このデモプログラムは、インポートされたモジュールのバージョンを表示します。

import demopkg1
print 'demopkg1:', demopkg1.__file__

import demopkg1.shared
print 'demopkg1.shared:', demopkg1.shared.__file__

import demopkg1.not_shared
print 'demopkg1.not_shared:', demopkg1.not_shared.__file__

シンプルな実行スクリプトを利用して、2つのパッケージ間を切り替えます。


export PYTHONPATH=os_${1}
echo "PYTHONPATH=$PYTHONPATH"
echo

python pkgutil_os_specific.py

引数に "one""two" を渡して実行すると、そのパスが適切に設定されます。

$ ./with_os.sh one
PYTHONPATH=os_one

demopkg1.__path__ before:
['.../PyMOTW/pkgutil/demopkg1']

demopkg1.__path__ after:
['.../PyMOTW/pkgutil/demopkg1',
 '.../PyMOTW/pkgutil/os_one/demopkg1',
 'demopkg']

demopkg1           : .../PyMOTW/pkgutil/demopkg1/__init__.pyc
demopkg1.shared    : .../PyMOTW/pkgutil/demopkg1/shared.pyc
demopkg1.not_shared: .../PyMOTW/pkgutil/os_one/demopkg1/not_shared.pyc
$ ./with_os.sh two
PYTHONPATH=os_two

demopkg1.__path__ before:
['.../PyMOTW/pkgutil/demopkg1']

demopkg1.__path__ after:
['.../PyMOTW/pkgutil/demopkg1',
 '.../PyMOTW/pkgutil/os_two/demopkg1',
 'demopkg']

demopkg1           : .../PyMOTW/pkgutil/demopkg1/__init__.pyc
demopkg1.shared    : .../PyMOTW/pkgutil/demopkg1/shared.pyc
demopkg1.not_shared: .../PyMOTW/pkgutil/os_two/demopkg1/not_shared.pyc

PKG ファイルは、通常の検索パスの任意の場所に置けます。カレントのワークディレクトリにある PKG ファイルは、開発ツリーを含めるためにも使用できます。

ネストされたパッケージ

ネストされたパッケージのために、トップレベルパッケージのパスを変更する必要があります。例えば、次のディレクトリ構造では、

$ find nested -name '*.py'
nested/__init__.py
nested/second/__init__.py
nested/second/deep.py
nested/shallow.py

ここで nested/__init__.py は次のようになります。

import pkgutil

__path__ = pkgutil.extend_path(__path__, __name__)
__path__.reverse()

そして、開発ツリーは次のようになります。

$ find develop/nested -name '*.py'
develop/nested/__init__.py
develop/nested/second/__init__.py
develop/nested/second/deep.py
develop/nested/shallow.py

shallowdeep の両モジュールは、そのモジュールがインストール済みバージョンか、開発バージョンかを表すメッセージを出力するシンプルな関数を提供します。

このテストプログラムは、新しいパッケージを実行します。

import nested

import nested.shallow
print 'nested.shallow:', nested.shallow.__file__
nested.shallow.func()

print
import nested.second.deep
print 'nested.second.deep:', nested.second.deep.__file__
nested.second.deep.func()

パス操作を行わずに pkgutil_nested.py が実行されると、両モジュールのインストール済みバージョンが使用されます。

$ python pkgutil_nested.py
nested.shallow: .../PyMOTW/pkgutil/nested/shallow.pyc
This func() comes from the installed version of nested.shallow

nested.second.deep: .../PyMOTW/pkgutil/nested/second/deep.pyc
This func() comes from the installed version of nested.second.deep

develop ディレクトリがその検索パスに追加されると、開発バージョンの両関数がインストール済みバージョンを上書きします。

$ PYTHONPATH=develop python pkgutil_nested.py
nested.shallow: .../PyMOTW/pkgutil/develop/nested/shallow.pyc
This func() comes from the development version of nested.shallow

nested.second.deep: .../PyMOTW/pkgutil/develop/nested/second/deep.pyc
This func() comes from the development version of nested.second.deep

パッケージのデータ

コードに加えて、Python パッケージは、テンプレート、デフォルト設定ファイル、画像ファイル、パッケージのコードで使用するその他のファイルといった、データファイルを含められます。 get_data() 関数は、フォーマットに依存せず、ファイルのデータへのアクセスを提供します。そのため、パッケージが EGG ファイル、バイナリの一部、もしくはファイルシステム上の通常ファイルで配布されるかどうかといったことは問題になりません。

パッケージ pkgwithdatatemplates ディレクトリを含みます。

$ find pkgwithdata -type f

pkgwithdata/__init__.py
pkgwithdata/templates/base.html

そして pkgwithdata/templates/base.html も含みます。

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title>PyMOTW Template</title>
</head>

<body>
<h1>Example Template</h1>

<p>This is a sample data file.</p>

</body>
</html>

このプログラムは、テンプレートのコンテンツを取り出すために get_data() を利用して、その内容を表示します。

import pkgutil

template = pkgutil.get_data('pkgwithdata', 'templates/base.html')
print template.encode('utf-8')

get_data() の引数は、パッケージのドット名で、パッケージのトップから相対的なファイル名になります。返り値はバイトシーケンスなので、表示する前に UTF-8 でエンコードします。

$ python pkgutil_get_data.py
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title>PyMOTW Template</title>
</head>

<body>
<h1>Example Template</h1>

<p>This is a sample data file.</p>

</body>
</html>

get_data() は、フォーマット非依存な配布形式です。というのは、パッケージのコンテンツへアクセスするのに PEP 302 で定義されたインポートフックを使用するからです。インポートフックを提供する任意のローダは、 zipfile の ZIP アーカイブインポータを含めて使用できます。

import pkgutil
import zipfile
import sys

# カレントディレクトリからコードとローカルのファイルシステムに
# 存在しない名前を使用するテンプレートを含む ZIP ファイルを作成する
with zipfile.PyZipFile('pkgwithdatainzip.zip', mode='w') as zf:
    zf.writepy('.')
    zf.write('pkgwithdata/templates/base.html',
             'pkgwithdata/templates/fromzip.html',
             )

# インポートパスへ ZIP ファイルを追加する
sys.path.insert(0, 'pkgwithdatainzip.zip')

# ZIP アーカイブから来ていることを表示するために pkgwithdata をインポートする
import pkgwithdata
print 'Loading pkgwithdata from', pkgwithdata.__file__

# テンプレートの本文を表示する
print '\nTemplate:'
print pkgutil.get_data('pkgwithdata', 'templates/fromzip.html').encode('utf-8')

このサンプルは、テンプレートファイルの名前を変更したファイルを含め、 pkgwithdata パッケージのコピーと一緒に ZIP アーカイブを作成します。それから、テンプレートを読み込むために pkgutil の使用前にインポートパスへ ZIP アーカイブを追加して、そのテンプレートの内容を表示します。

$ python pkgutil_get_data_zip.py
Loading pkgwithdata from pkgwithdatainzip.zip/pkgwithdata/__init__.pyc

Template:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title>PyMOTW Template</title>
</head>

<body>
<h1>Example Template</h1>

<p>This is a sample data file.</p>

</body>
</html>

See also

pkgutil
本モジュールの標準ライブラリドキュメント
virtualenv
Ian Bicking の仮想環境スクリプト
distutils
標準ライブラリのパッケージングツール
Distribute
次世代パッケージングツール
PEP 302
インポートフック
zipfile
インポートできる ZIP アーカイブを作成
zipimport
ZIP アーカイブからパッケージをインポート
Bookmark and Share