[Python-Dev] zipimport, round 4

Just van Rossum just@letterror.com
Mon, 9 Dec 2002 23:51:21 +0100


--11364638-0-3248466697=:9007
Content-Type: text/plain; Charset=US-ASCII
Content-Transfer-Encoding: 7bit

Here's a new patch. Changes include:
- pyc time stamp checking (thanks Skip and </F>!)
- better python -v output
- also works when zipimport is built dynamically

I've written a note about various aspects of the patch (pasted below) but I'm
not sure it's PEP-ready yet. Comments are more than welcome!

Just

---------------------

This note is in a way an addendum to PEP 273. I fully agree the points and goals
of the PEP, except for the sections "Efficiency", "Directory Imports" and
"Custom Imports". However, I disagree strongly with much of the design and
implementation of PEP 273. This note presents an alternative, with a matching
implementation.


A brief history of import.c -- digging through its cvs log.

When Python was brand new, there were only builtin modules and .py files. Then
.pyc support was added and about half a year later support for dynamically
loaded C extension was implemented. Then came frozen modules. Then Guido rewrote
much of import.c, introducing the filedescr struct {suffix, mode, type},
allowing for some level of (builtin) extension of the import mechanism. This was
just before Python 1.1 was released. Since then, the only big change has been
package support (in 1997), which added a lot of complexity. (The __import__ hook
was quietly added in 1995, it's not even mentioned in the log entry of ceval.c
r2.69, I had to do a manual binary search to find it...)

All later import extensions were either implemented using the filedescr
mechanism and/or hardcoded in find_module and load_module. This ranges from
reading byte code from Macintosh resources to Windows registry-based imports.
Every single time this involved another test in find_module() and another branch
in the load_module() switch. "This has to stop."


The PEP 273 implementation.

Obviously the PEP 273 implementation has to add *something* to import.c, but it
makes a few mistakes:

  - it's badly factored (for example it adds the full implementation of
    reading zip files to import.c.)
  - it adds a questionable new feature: directory caching. The original
    author claimed this was needed for zip imports, but instead solving
    the problem locally for zip files the feature is added to the builtin
    import mechanism as a whole. Although this causes some speedup
    (especially for network file system imports), this is bad, for several
    reasons:
    - it's not strictly *needed* for builtin import
    - it's not a frequent feature request from users (as far as I know)
    - it makes import.c even more complicated than it already is (note that
      I say "complicated", not "complex")
    - it changes semantics: if a module is added to the file system *after*
      the directory contents has been cached, it will not be found. This
      might only be a real issue for an IDE that runs code inside the IDE
      process, but still.


A different approach.

An __import__ hook is close to useless for the problem at hand, as it needs to
reimplement much of import.c from scratch. This can be witnessed in Guido's old
ihooks.py, Greg Stein's imputils.py and Gordon McMillan's iu.py, each of which
are failry complex, and not compatible with any other. So we either have to add
just another import type to import.c for zip archives, or we can add a more
general import hook. Let's assume for a moment we want to do the *former*.

The most important goal is for zip file names on sys.path and PYTHONPATH to
"just work" -- as if a zip archive is just another directory. So when traversing
sys.path, each item must be checked for zip-file-ness, and if it is, the zip
file's file index needs to be read so we can determine whether the module being
imported is in there.

I went for an OO approach, and represent a zip file with an instance of the
zipimporter class. Obviously it's quite expensive to read the zip file index
again and again, so we have to maintain a cache of zipimporter objects. The most
Pythonic approach would be to use a dict, using the sys.path item as the key.
This cache could be private to the zip import mechanism, but it makes sense to
also cache the fact that a sys.path item is *not* a zip file. A simple solution
is to map such a path item to None. By now it makes more sense to have this
cache available in import.c.


The zipimporter protocol.

The zipimporter's constructor takes one argument: a path to a zip archive. It
will raise an exception if the file is not found or if it's not a zip file.

The import mechanism works in two steps: 1) find the module, 2) if found, load
the module. The zipimporter object follows this pattern, it has two methods: 

    find_module(name):
        Returns None if the module wasn't found, or the
        zipimporter object itself if it was.

    load_module(fullname):
        Load the module, return it (or propagate an exception).

The main path traversing loop in import.c will then look like this (omitting the
caching mechanics for brevity):

    def find_module(name, path):
        if isbuiltin(name):
            return builtin_filedescr
        if isfrozen(name):
            return frozen_filedescr
        if path is None:
            path = sys.path
        for p in sys.path:
            try:
                v = zipimporter(p)
            except ZipImportError:
                pass
            else:
                w = v.find_module(name)
                if w is not None:
                    return w
            ...handle builtin file system import...


Packages.

Paths to subdirectories of the zip archive must also work, on sys.path for one,
but most importantly for pkg.__path__. For example: "Archive.zip/distutils/".
Such a path will most likely be added *after* "StdLib.zip" has been read (after
all, the parent package is *always* loaded before any submodules), so all I need
to do is strip the sub path, and look up the bare .zip path in the cache. A
*new* zipimporter instance is then created, which references the same (internal,
but visible) file directory info as the "bare" zipimporter object. A .prefix
contains the sub path:

  >>> from zipimport import zipimporter
  >>> z = zipimporter("Archive.zip/distutils")
  >>> z.archive
  'Archive.zip'
  >>> z.prefix
  'distutils/'
  >>> 


Beyond zipimport.

So there we are, zipimport works, with just a relatively minor impact on
import.c. The obvious next question is: what about other import types, whether
future needs for the core, or third party needs? It turns out the above approach
is *easily* generalized to handle *arbitrary* path-based import hooks. Instead
of just checking for zip-ness, it can check a list of candidates (again, caching
cruft omitted):

    def find_module(name, path):
        if isbuiltin(name):
            return builtin_filedescr
        if isfrozen(name):
            return frozen_filedescr
        if path is None:
            path = sys.path
        for p in sys.path:
            v = None
            for hook in sys.import_hooks:
                try:
                    v = hook(p)
                except ImportError:
                    pass
                else:
                    break
            if v is not None:
                w = v.find_module(name)
                if w is not None:
                    return w
            ...handle builtin file system import...

Now, one tiny step further, and we have something that fairly closely mimics
Gordon McMillan's iu.py. That tiny step is what Gordon calls the "metapath". It
works like this:

    def find_module(name, path):
        for v in sys.meta_path:
            w = v.find_module(name, path)
            if w is not None:
                return w
        # fall through to builtin behavior
        if isbuiltin(name):
            return builtin_filedescr
        [ rest same as above ]

An item on sys.meta_path can override *anything*, and does not need an item on
sys.path to get invoked. The find_module() method of such an object has an extra
argument: path. It is None or parent.__path__. If it's None, sys.path is
implied.


The Patch.

I've modified import.c to support all of the above. Even the path handler cache
is exposed: sys.path_importers. (This is what Gordon calls "shadowpath"; I'm not
happy with either name, I'm open to suggestions.) The sys.meta_path addition
isn't strictly neccesary, but it's a useful feature and I think generalizes and
exposes the import mechanism to the maximum of what is possible with the current
state of import.c.

The protocol details are open to discussion. They are partly based on what's
relatively easily doable in import.c. Other than that I've tried to follow
common sense as to what is practical for writing import hooks.

The patch is not yet complete, especially regarding integration with the imp
module: you can't currently use the imp module to invoke any import hook. I have
some ideas on how to do this, but I'd like to focus on the basics first. Also:
the reload() function is currently not supported. This will be easy to fix
later.

I *thought* about allowing objects on sys.path (which would then work as an
importer object) but for now I've not done it as sys.meta_path makes it somewhat
redundant. It would be easy to do, though: it would add another 10 lines or so
to import.c.

I've tested the zipimporter module both as a builtin and as a shared lib: it
works for me in both configurations. But when building it dynamically: it _has_
to be available on sys.path *before* site.py is run. When running from the build
dir on unix: add the appropriate build/lib.* dir to your PYTHONPATH and it
should work.
--11364638-0-3248466697=:9007
Content-Type: application/octet-stream; Name="zipper4.tgz"; X-Mac-Type="00000000"; X-Mac-Creator="00000000"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; Filename="zipper4.tgz"

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
58/nz3/N538B2FEpBwCgAAA=
--11364638-0-3248466697=:9007--