[Tutor] (getch() equivalent) [fun with imports]

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Sun Mar 9 02:12:02 2003


On Thu, 20 Feb 2003, Tony Cappellini wrote:

> In your post here
> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/134892
>
> why do you import msvcrt in both functions ?
> Why not just import it once ?

Hi Tony,

Sorry for such a late reply!  I hope that you still remember your
question.  *grin* I'm forwarding this to tutor@python.org too since this
might be helpful to someone else.


Let's take a quick look at the source code just so we can point a what
we're talking about:

###
class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()
###

Your question is asking why we do the import twice.  The reason is because
the imports are being done locally each method, so the module's only going
to be known "locally".  Here's a concrete example that shows what I mean:

###
>>> def test_internal_import():
...     import md5
...     m = md5.new()
...     m.update("this is a ")
...     m.update("test")
...     return m.hexdigest()
...
>>> test_internal_import
<function test_internal_import at 0x1e9d90>
>>> test_internal_import()
'54b0c58c7ce9f2a8b551351102ee0938'
>>> md5
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'md5' is not defined
###

The 'import' statement introduces a new variable in the place that we're
at; in almost every case, we do it "toplevel" so that the module's
definition can be acccessed everywhere.  But we also have the ability to
do an 'import' locally in a function, if we ever feel the inclination.
*grin*


Just as local variables are visible only with the enclosing function,
local module imports are treated with the same rule.  Doing 'import'
within a function very unusual: it's not often useful to localize a module
to a single function, since it's probably going to be used everywhere in a
program.


However, I could have written that code differently to avoid the double
import.  Here's one way to do it:

###
try:
    import msvcrt
catch:
    msvcrt = None
                   ## By this time, I can assume msvcrt is defined,
                   ## although it might be set to None...
class _GetchWindows:
    def __init__(self):
        if msvcrt == None:
            raise ImportError, "msvcrt module not available"

    def __call__(self):
        return msvcrt.getch()
###

This attitude toward missing imports is similiar to the approach taken by
the pygame folk, although pygame handles a missing module with much more
sophistication.

For example, pygame optionally supports a "font"  library... but the
client might not have the font library installed!  In that case, pygame
sets the 'pygame.font' library to a proxy library that doesn't do
anything:

###
try: import pygame.font
except (ImportError,IOError), msg: font=MissingModule("font", msg, 0)
###

(http://cvs.seul.org/cgi-bin/cvsweb-1.80.cgi/games/pygame/lib/__init__.py?rev=1.25&content-type=text/x-cvsweb-markup)


Pygame's "MissingModule" class is actually pretty cute: it mimics a real
module, but starts tossing out warnings to the user whenever we try using
its functionality:

###
class MissingModule:
    def __init__(self, name, info='', urgent=0):
        self.name = name
        self.info = str(info)
        self.urgent = urgent
        if urgent:
            self.warn()

    def __getattr__(self, var):
        if not self.urgent:
            self.warn()
            self.urgent = 1
        MissingPygameModule = "%s module not available" % self.name
        raise NotImplementedError, MissingPygameModule

    def __nonzero__(self):
        return 0

    def warn(self):
        if self.urgent: type = 'import'
        else: type = 'use'
        message = '%s %s: %s' % (type, self.name, self.info)
        try:
            import warnings
            if self.urgent: level = 4
            else: level = 3
            warnings.warn(message, RuntimeWarning, level)
        except ImportError:
            print message
###

(It's a slight shame that MissingModule lives only in the __init__.py
file: I think it belongs proper in the Python Cookbook as an example of
permissive imports!)

Sorry, I went way off tangent again, but I thought that it was
interesting...  *grin* Does this clear up some things?


Good luck!