zipimport – Load Python code from inside ZIP archives

Purpose:Load Python code from inside ZIP archives.
Available In:2.3 and later

The zipimport module implements the zipimporter class, which can be used to find and load Python modules inside ZIP archives. The zipimporter supports the “import hooks” API specified in PEP 302; this is how Python Eggs work.

You probably won’t need to use the zipimport module directly, since it is possible to import directly from a ZIP archive as long as that archive appears in your sys.path. However, it is interesting to see the features available.

Example

For the examples this week, I’ll reuse some of the code from last week’s discussion of zipfile to create an example ZIP archive containing some Python modules. If you are experimenting with the sample code on your system, run zipimport_make_example.py before any of the rest of the examples. It will create a ZIP archive containing all of the modules in the example directory, along with some test data needed for the code below.

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

Finding a Module

Given the full name of a module, find_module() will try to locate that module inside the ZIP archive.

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)

If the module is found, the zipimporter instance is returned. Otherwise, None is returned.

$ python zipimport_find_module.py

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

Accessing Code

The get_code() method loads the code object for a module from the archive.

import zipimport

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

The code object is not the same as a module object.

$ python zipimport_get_code.py

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

To load the code as a usable module, use load_module() instead.

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

The result is a module object as though the code had been loaded from a regular import:

$ 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>

Source

As with the inspect module, it is possible to retrieve the source code for a module from the ZIP archive, if the archive includes the source. In the case of the example, only zipimport_get_source.py is added to zipimport_example.zip (the rest of the modules are just added as the .pyc files).

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

If the source for a module is not available, get_source() returns 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

Packages

To determine if a name refers to a package instead of a regular module, use is_package().

import zipimport

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

In this case, zipimport_is_package came from a module and the example_package is a package.

$ python zipimport_is_package.py

zipimport_is_package False
example_package True

Data

There are times when source modules or packages need to be distributed with non-code data. Images, configuration files, default data, and test fixtures are just a few examples of this. Frequently, the module __path__ attribute is used to find these data files relative to where the code is installed.

For example, with a normal module you might do something like:

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()

The output will look something like this, with the path changed based on where the PyMOTW sample code is on your filesystem.

$ python zipimport_get_data_nozip.py

/Users/dhellmann/Documents/PyMOTW/src/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.

If the example_package is imported from the ZIP archive instead of the filesystem, that method does not work:

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()

The __file__ of the package refers to the ZIP archive, and not a directory. So we cannot just build up the path to the README.txt file.

$ 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'

Instead, we need to use the get_data() method. We can access zipimporter instance which loaded the module through the __loader__ attribute of the imported module:

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.

The __loader__ is not set for modules not imported via zipimport.

See also

zipimport
Standard library documentation for this module.
imp
Other import-related functions.
PEP 302
New Import Hooks
pkgutil
Provides a more generic interface to get_data().