Distinguish between ImportError because of not fou

2019-04-11 02:16发布

问题:

I have a few modules in python, which are imported dynamicly and all have the same structur (plugin.py, models.py, tests.py, ...). In the managing code i want to import those submodules, but for example models.py or tests.py is not mandatory. (So i could have plugin_a.plugin and plugin_a.tests but only plugin_b.plugin).

I can check if the submodule exists by

try:
    __import__(module_name + ".tests")
except ImportError:
    pass

That will fail, if module_name+".tests" is not found, but it will also fail if the tests-module itself will try to import something, which is not found, for example because of a typo. Is there any way to check if the module exists, without importing it or make sure, the ImportError is only raised by one specific import-action?

回答1:

You know what the import error message will look like if the module doesn't exist so just check for that:

try:
    module = module_name + '.tests'
    __import__(module)
except ImportError, e:
    if e.args and e.args[0] == 'No module named ' + module:
        print(module, 'does not exist')
    else:
        print(module, 'failed to import')


回答2:

You can see from the length of the traceback how many levels deep the import failed. A missing .test module has a traceback with just one frame, a direct dependency failing has two frames, etc.

Python 2 version, using sys.exc_info() to access the traceback:

import sys


try:
    __import__(module_name + ".tests")
except ImportError:
    if sys.exc_info()[-1].tb_next is not None:
        print "Dependency import failed"
    else:
        print "No module {}.tests".format(module_name)

Python 3 version, where exceptions have a __traceback__ attribute:

try:
    __import__(module_name + ".tests")
except ImportError as exc:
    if exc.__traceback__.tb_next is not None:
        print("Dependency import failed")
    else:
        print("No module {}.tests".format(module_name))

Demo:

>>> import sys
>>> def direct_import_failure(name):
...     try:
...         __import__(name)
...     except ImportError:
...         return sys.exc_info()[-1].tb_next is None
... 
>>> with open('foo.py', 'w') as foo:
...     foo.write("""\
... import bar
... """)
... 
>>> direct_import_failure('bar')
True
>>> direct_import_failure('foo')
False


回答3:

Following on from: How to check if a python module exists without importing it

The imp.find_module function will either return a 3-element tuple (file, pathname, description) or raise an ImportError.

It will not raise an error if there is a problem with the module, only if it doesn't exist.

The python docs suggest that you should first find and import the package and then use its path in second find_module, doing this recursively if necessary.

This seems a little messy to me.

The function below will check for the existence of a given module, at any level of relative import (module.py, package.module.py, package.subpackage.module.py etc.).

imp.find_module returns an open file, which you could use in imp.load_module, but this, too seems a little clunky, so I close the file so that it can be imported outside of the function.

Note that this isn't perfect. if you are looking for package.subpackage.module but actually package.subpackage is a valid module it will also return true.

import imp
import importlib

def module_exists(modulename):
    modlist = modulename.split('.')
    pathlist = None
    for mod in modlist:
        print mod
        try:
            openfile, pathname, desc = imp.find_module(mod,pathlist)
            pathlist = [pathname]
        except ImportError:
            print "Module '{}' does not exist".format(mod)
            return(False)
        else:
            print 'found {}'.format(openfile)
            if openfile:
                openfile.close()
                return(True)

if __name__ == '__main__':
    mymodule = 'parrot.type.norwegian_blue'
    if module_exists(mymodule):
        importlib.import_module(mymodule)

Note also that I'm using importlib.import_module instead of __import__.

Finally, note that importlib is Python 2.7 and upwards



回答4:

Is there any way to check if the module exists, without importing it or make sure, the ImportError is only raised by one specific import-action?

There could be multiple reasons why ImportError fails because importing will evaluate the module; if there is a syntax error the module will fail to load.

To check if a module exists without loading it, use pkgutil.find_loader, like this:

>>> pkgutil.find_loader('requests')
<pkgutil.ImpLoader instance at 0x9a9ce8c>
>>> pkgutil.find_loader('foo')

It will return either a ImpLoader instance, or None if the package is not importable. You can get further details from the ImpLoader instance, like the path of the module:

>>> pkgutil.find_loader('django').filename
'/usr/local/lib/python2.7/dist-packages/django'