zipimport – ZIP アーカイブ内から Python コードを読み込む

目的:ZIP アーカイブ内から Python コードを読み込む
利用できるバージョン:2.3 以上

zipimport モジュールは、ZIP アーカイブ内から Python モジュールを探して、そのモジュールを読み込む zipimporter クラスを実装します。 zipimporter は、 PEP 302 で標準化された “import hooks” API をサポートします。これは Python Eggs の動作を説明するものです。

もしかしたら zipimport モジュールを直接的に使用する必要はないかもしれません。 sys.path にある ZIP アーカイブから直接インポートできるからです。とは言え、その機能をみていくのも興味深いものです。

サンプル

Python モジュールを含むサンプルの ZIP アーカイブを作成するために、今週のサンプルは、先週の zipfile の記事のコードを再利用します。自分のシステム上でサンプルコードで試してみたいなら、任意のサンプルを試す前に zipimport_make_example.py を実行してください。このスクリプトは、ソースコードに必要なテストデータと一緒に、対象ディレクトリの全てのモジュールを含む ZIP アーカイブを作成します。

import sys
import zipfile

if __name__ == '__main__':
    zf = zipfile.PyZipFile('zipimport_example.zip', mode='w')
    try:
        zf.writepy('.')
        zf.write('zipimport_get_source.py')
        zf.write('example_package/README.txt')
    finally:
        zf.close()
    for name in zf.namelist():
        print name
$ python zipimport_make_example.py
__init__.pyc
example_package/__init__.pyc
zipimport_find_module.pyc
zipimport_get_code.pyc
zipimport_get_data.pyc
zipimport_get_data_nozip.pyc
zipimport_get_data_zip.pyc
zipimport_get_source.pyc
zipimport_is_package.pyc
zipimport_load_module.pyc
zipimport_make_example.pyc
zipimport_get_source.py
example_package/README.txt

モジュールを探す

モジュールの完全な名前を渡すと、 find_module() は ZIP アーカイブ内にあるモジュールを展開しようとします。

import zipimport

importer = zipimport.zipimporter('zipimport_example.zip')

for module_name in [ 'zipimport_find_module', 'not_there' ]:
    print module_name, ':', importer.find_module(module_name)

指定したモジュールが見つかると zipimporter インスタンスが返されます。それ以外の場合は None 返されます。

$ python zipimport_find_module.py
zipimport_find_module : <zipimporter object "zipimport_example.zip">
not_there : None

コードにアクセスする

get_code() メソッドは、アーカイブ内のモジュールのコードオブジェクトを読み込みます。

import zipimport

importer = zipimport.zipimporter('zipimport_example.zip')
code = importer.get_code('zipimport_get_code')
print code

コードオブジェクトは、モジュールオブジェクトと同じではありません。

$ python zipimport_get_code.py
<code object <module> at 0x1002bc2b0, file "./zipimport_get_code.py", line 7>

普通に利用可能なモジュールとして読み込むには、 load_module() を使用してください。

import zipimport

importer = zipimport.zipimporter('zipimport_example.zip')
module = importer.load_module('zipimport_get_code')
print 'Name   :', module.__name__
print 'Loader :', module.__loader__
print 'Code   :', module.code

この実行結果は、普通にインポートして読み込んだコードのようにモジュールオブジェクトになります。

$ python zipimport_load_module.py
<code object <module> at 0x1002ea7b0, file "./zipimport_get_code.py", line 7>
Name   : zipimport_get_code
Loader : <zipimporter object "zipimport_example.zip">
Code   : <code object <module> at 0x1002ea7b0, file "./zipimport_get_code.py", line 7>

ソース

inspect と同様に、ZIP アーカイブがソースコードを含む場合、そのアーカイブからモジュールのソースコードを取り出せます。このサンプルでは、 zipimport_get_source.py のみ zipimport_example.zip に追加されています (その他のモジュールは .pyc ファイルのみを追加しています) 。

import zipimport

importer = zipimport.zipimporter('zipimport_example.zip')
for module_name in ['zipimport_get_code', 'zipimport_get_source']:
    source = importer.get_source(module_name)
    print '=' * 80
    print module_name
    print '=' * 80
    print source
    print

モジュールのソースが利用できない場合、 get_source()None を返します。

$ python zipimport_get_source.py
================================================================================
zipimport_get_code
================================================================================
None

================================================================================
zipimport_get_source
================================================================================
#!/usr/bin/env python
#
# Copyright 2007 Doug Hellmann.
#

"""Retrieving the source code for a module within a zip archive.

"""
#end_pymotw_header

import zipimport

importer = zipimport.zipimporter('zipimport_example.zip')
for module_name in ['zipimport_get_code', 'zipimport_get_source']:
    source = importer.get_source(module_name)
    print '=' * 80
    print module_name
    print '=' * 80
    print source
    print

パッケージ

普通のモジュールではなく、ある名前がパッケージを参照するかどうかを調べるには、 is_package() を使用してください。

import zipimport

importer = zipimport.zipimporter('zipimport_example.zip')
for name in ['zipimport_is_package', 'example_package']:
    print name, importer.is_package(name)

このサンプルでは、 zipimport_is_package はモジュールであり、 example_package はパッケージです。

$ python zipimport_is_package.py
zipimport_is_package False
example_package True

データ

ソースモジュールやパッケージが、ソースコード以外のデータと一緒に配布されることもあります。例えば、画像、設定ファイル、デフォルトのデータ、テストのフィクスチャなどです。よくやるのは、そのモジュールの __file__ 属性が、ソースコードのインストース場所に関連するデータファイルを探すのに使用されます。

例えば、通常のモジュールと一緒に次のようにするかもしれません。

import os
import example_package
data_filename = os.path.join(os.path.dirname(example_package.__file__), 
                             'README.txt')
print data_filename, ':'
print open(data_filename, 'rt').read()

この出力結果は次のように、ファイルシステム上の PyMOTW のサンプルコードに基づいたパスに変更されます。

$ python zipimport_get_data_nozip.py
/Users/dhellmann/Devel/pymotw-ja/t2y/PyMOTW/zipimport/example_package/README.txt :
This file represents sample data which could be embedded in the ZIP
archive.  You could include a configuration file, images, or any other
sort of non-code data.

example_package が、ファイルシステムではなく ZIP アーカイブからインポートされる場合、そのモジュールは動作しません。

import sys
sys.path.insert(0, 'zipimport_example.zip')

import os
import example_package
print example_package.__file__
data_filename = os.path.join(os.path.dirname(example_package.__file__), 
                             'README.txt')
print data_filename, ':'
print open(data_filename, 'rt').read()

パッケージの __file__ は、ディレクトリではなく ZIP アーカイブを参照するので、単純に README.txt ファイルへのパスを構築できません。

$ python zipimport_get_data_zip.py
zipimport_example.zip/example_package/__init__.pyc
zipimport_example.zip/example_package/README.txt :
Traceback (most recent call last):
  File "zipimport_get_data_zip.py", line 40, in <module>
    print open(data_filename, 'rt').read()
IOError: [Errno 20] Not a directory: 'zipimport_example.zip/example_package/README.txt'

その代わりに get_data() メソッドを使用する必要があります。インポートされたモジュールの __loader__ 属性を通して、そのモジュールを読み込んだ zipimporter インスタンスにアクセスできます。

import sys
sys.path.insert(0, 'zipimport_example.zip')

import os
import example_package
print example_package.__file__
print example_package.__loader__.get_data('example_package/README.txt')
$ python zipimport_get_data.py
zipimport_example.zip/example_package/__init__.pyc
This file represents sample data which could be embedded in the ZIP
archive.  You could include a configuration file, images, or any other
sort of non-code data.

__loader__ は、 zipimport 経由でインポートされていないモジュールにはセットされません。

See also

zipimport
本モジュールの標準ライブラリドキュメント
imp
その他のインポート関連の機能
PEP 302
新たなインポートフック
pkgutil
get_data() に対するより汎用的なインタフェースを提供
Bookmark and Share