4

In order to add to my program (in python 2.7) a check for available modules, I added the following code in place of the classical import (the idea being to help someone to locate & add extra modules):

mymodules = ['socket', 'requests', 'simplejson', 'pickle', 'IPy',
    'pygeoip', 'urllib', 'time', 'urllib2', 'StringIO', 'gzip', 'os']

import sys, importlib   # these ones should be available, otherwise bad luck :)
for module in mymodules:
    try:
        importlib.import_module(module)
        print "importing ", module
    except:
        if module == "requests": info = "http://docs.python-requests.org/en/latest/user/install/#install or aptitude install python-requests"
        elif module == "requests": info = "https://github.com/simplejson/simplejson or aptitude install python-simplejson"
        elif module == "IPy": info = "https://github.com/haypo/python-ipy/wiki or aptitude install python-ipy"
        elif module == "pygeoip": info = "https://github.com/appliedsec/pygeoip or pip install pygeoip"
        else: info = "Oops, you should not see this - the description of the missing plugin is missing in the code"
        print "module {} is missing, see {}".format(module,info)
        sys.exit(0)

Later on my program crashes with NameError on a call to time.time() ('time' is not defined). I therefore tried to test module importing from scratch:

>>> import sys, importlib
>>> importlib.import_module("time")
<module 'time' (built-in)>
>>> print sys.modules.keys()
['copy_reg', 'sre_compile', '_sre', 'encodings', 'site', '__builtin__', 'sysconfig', '__main__', 'encodings.encodings', 'abc', 'importlib.sys', 'posixpath', '_weakrefset', 'errno', 'encodings.codecs', 'sre_constants', 're', '_abcoll', 'types', '_codecs', 'encodings.__builtin__', '_warnings', 'genericpath', 'stat', 'zipimport', '_sysconfigdata', 'warnings', 'UserDict', 'encodings.utf_8', 'sys', 'codecs', 'readline', '_sysconfigdata_nd', 'os.path', 'importlib', 'sitecustomize', 'signal', 'traceback', 'linecache', 'posix', 'encodings.aliases', 'time', 'exceptions', 'sre_parse', 'os', '_weakref']

time is there. Nevertheless:

>>> print time.time()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'time' is not defined

Now with a classical import:

>>> import time
>>> print time.time()
1380831191.08

Why doesn't importlib.import_module("time") imports time in such a way that time.time() can be called?

WoJ
  • 27,165
  • 48
  • 180
  • 345
  • You're right, I can recreate this. And it isn't limited to the 'time' module, it's as if import_module() returns the module instead of importing it as a global (`time= importlib.import_module("time")` would work) – yuvi Oct 04 '13 at 10:56
  • A few comments about your code: Put the module urls in a dict, so you can look them up instead of having multiple `if` statements checking the name. Furthermore, don't exit after the first failing module but accumulate the missing modules in a list, then you can notify the user of all missing modules at once. Also, there should be no need to test or _dynamically_ import builtin modules like `os`, `socket` etc - just use a normal import statement, if these modules fail you'll probably want to see the exception message as it can indicate serious problems with the used python installation. – l4mpi Oct 04 '13 at 12:34
  • @l4mpi: Thanks for the constructive comments. Ad 1: I assume you mean a dict where the module name is the key and the value the information to display? Good idea, I will try that. Ad 2: Also a good idea :) Ad 3: I thought about that initially, then put all the modules together for simplicity. Your comment about an actual fail on this module (with traceback) makes sense, will do that too. – WoJ Oct 04 '13 at 12:42

3 Answers3

10

From the docs:

The specified module will be inserted into sys.modules and returned.

In other words, import_module will not create a variable for you, you will have to do it yourself:

time = importlib.import_module('time')

Or, in your "dynamic" case:

globals()['time'] = importlib.import_module('time')

On a side note, why do you even do this? Why not just wrap a normal import in a try-except block?

fjarri
  • 9,546
  • 39
  • 49
  • 1
    Thank you - it now works perfectly. As to why not wrapping `import` with `try`/`except`: I did not manage to have it accept a variable (also discussed [here](http://stackoverflow.com/questions/6677424/how-do-i-import-variable-packages-in-python-like-using-variable-variables-i)). I thought about using `__import__` but [the docs suggest importlib](http://docs.python.org/2/library/functions.html#__import__) – WoJ Oct 04 '13 at 12:10
3

I think importlib.import_module("time") returns an object, we need to assign it to a variable. Use that variable to invoke all the methods of time.

Try:

import sys, importlib

var_name = importlib.import_module("time")
print var_name.time()
Vivek
  • 910
  • 2
  • 9
  • 26
-1

Please for the love of god don't do this, its a waste of lines of code, its another place bugs can exist, and also the import system is subject to change. Its better if you just

import requests

and let it just fail then I know that requests is missing and I can look about or install it myself.

Jakob Bowyer
  • 33,878
  • 8
  • 76
  • 91
  • Downvote - dynamically checking the availability of modules is IMO a good use case for `importlib`, and 'don't do that, just use static imports' isn't exactly helpful advice if you're dealing with a huge amount of modules (of course you can wrap 20 different static imports in `try/except` blocks, but why not just use a list and a four-liner with importlib?). And "the import system is subject to change" is just plain wrong in this context - the fact that `importlib.import_module` will import a python module isn't going to change, and any change affecting it will likely affect `import` too. – l4mpi Oct 04 '13 at 12:06
  • 1
    I do not mind about the extra lines of code if I can help someone to use my module, at list by indicating him where to go next. – WoJ Oct 04 '13 at 12:13
  • What WoJ said. "Let it just fail then I know that requests is missing and I can look about or install it myself" isn't exactly user friendly, and simply unacceptable in some contexts (e.g. GUI programs, things intended for users who aren't programmers). And even as a programmer, I'd prefer the program to print a list of URLs for _all_ missing modules and exit gracefully, instead of crashing with an ImportError for only the first of maybe many missing modules, providing you with just a name I'd need to look up (which might be harder than you consider) and install. – l4mpi Oct 04 '13 at 12:23
  • @l4mpi If its for non programmers, then bundle the dependancies. If it is for programmers its just more bloat for me to dig through. – Jakob Bowyer Oct 04 '13 at 12:34
  • So you'd consider a message like "please install numpy, requests, pySNMP and [insert further modules here]" to be bloat, but would happily get ImportError after ImportError until you've finally found all required modules? – l4mpi Oct 04 '13 at 12:44
  • @Jakob Bowyer: as a non-programmer I have had my fair share of modules which crashed miserably on some obscure modules and I would have loved to have a pointer instead of googling around. I know I should bundle everything but not being an expert in Python I will probably miserably fail on something a `pip install`will handle gracefully. – WoJ Oct 04 '13 at 12:47