imp.find_module() which supports zipped eggs

2019-02-24 00:08发布

问题:

imp.find_module() does not find modules from zipped eggs.

How can find modules which can come from both places: directories or zipped eggs? It is important in my case that I can provide a path argument like imp.find_module() supports it.

Background

Somehow packages get installed twice in our environment. As zipped egg and as plain files. I want to write a check which tells me if a module is installed twice. See https://stackoverflow.com/a/23990989/633961

回答1:

Assuming Python 2, the information I think you need is in PEP 302 - New Import Hooks (the PEP is outdated for Python 3, which is completely different in this regard).

Finding and importing modules from ZIP archives is implemented in zipimport, which is "hooked" into the import machinery as described by the PEP. When PEP 302 and importing from ZIPs were added to Python, the imp modules was not adapted, i.e. imp is totally unaware of PEP 302 hooks.

A "generic" find_module function that finds modules like imp does and respects PEP 302 hooks, would roughly look like this:

import imp
import sys

def find_module(fullname, path=None):
    try:
        # 1. Try imp.find_module(), which searches sys.path, but does
        # not respect PEP 302 import hooks.
        result = imp.find_module(fullname, path)
        if result:
            return result
    except ImportError:
        pass
    if path is None:
        path = sys.path
    for item in path:
        # 2. Scan path for import hooks. sys.path_importer_cache maps
        # path items to optional "importer" objects, that implement
        # find_module() etc.  Note that path must be a subset of
        # sys.path for this to work.
        importer = sys.path_importer_cache.get(item)
        if importer:
            try:
                result = importer.find_module(fullname, [item])
                if result:
                    return result
            except ImportError:
                pass
    raise ImportError("%s not found" % fullname)

if __name__ == "__main__":
    # Provide a simple CLI for `find_module` above.
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--path", action="append")
    parser.add_argument("modname", nargs='+')
    args = parser.parse_args()
    for name in args.modname:
        print find_module(name, args.path)

Note, though, that the result from finding a module in a ZIP archive looks quite different to what imp.find_module returns: you'll get a zipimport.zipimporter object for the particular ZIP. The litte program above prints the following when asked to find a regular module, a built-in module and a module from a zipped egg:

$ python find_module.py grin os sys
<zipimporter object "<my venv>/lib/python2.7/site-packages/grin-1.2.1-py2.7.egg">
(<open file '<my venv>/lib/python2.7/os.py', mode 'U' at 0x10a0bbf60>, '<my venv>/lib/python2.7/os.py', ('.py', 'U', 1))
(None, 'sys', ('', '', 6))