[Tutor] Inter-related modules

Magnus Lyckå magnus at thinkware.se
Fri May 7 14:06:56 EDT 2004


At 15:15 2004-05-02 +0800, msp wrote:
>I have a problem with several modules all needing to use
>another module.  This is quite common, but I'll use curses
>as an example as it's familiar to many.
>
>I start my main program which imports curses and calls initscr().
>It asks the user what they want to do, and they select something from
>module 'a'. Module 'a' needs to use the screen to interact with the user.
>If module 'a' imports curses, this copy of curses knows that initscr()
>hasn't been calledand fails. If it calls initscr(), I have two sets of curses
>talking to the screen at once. This may be OK for curses (it saves and
>restores the starting screen state) but not for some other modules which
>I also have this problem with.

This isn't really a problem with modules, but with any object, where
you just want one occurence. You could as well call some kind of
initialization routine several times in the same module, and run into
similar trouble.

Right? The problem is that you have some kind of shared resource, and
you want several clients to use the same resource, not to get a copy
each. This should prefereably happen without the different clients
needing to know about each other. (In the case of curses, I suppose
you will get total chaos if several parts of the code tries to update
the same screen independently of each other, but there are certainly
other cases when you want something like this to work.)

You should be clear about one thing though: You can only import a module
*once* in Python with the import statement. Let's imagine that you have
two modules, main and sub, and they look like this:

main.py:
-------------
import curses
import sub
def m():
     sub.s()
m()
-------------

sub.py:
-------------
import curses
def s():
     <some more code>
-------------

This code is equivalent with:

main.py:
-------------
import curses
import sub
sub.curses = curses
def m():
     sub.s()
m()
-------------

sub.py:
-------------
def s():
     <some more code>
-------------

Or prefereably...

main.py:
-------------
import curses
import sub
sub.init_curses(curses)
def m():
     sub.s()
m()
-------------

sub.py:
-------------
def init_curses(curses_module):
     global curses
     curses = curses_module
def s():
     <some more code>
-------------

On other words, unless you use the reload() function, you can only
import a module ONCE. Any subsequent call of 'import' on the same
module will only create a new variable name in the local scope which
refers to the import module you already loaded.

>Some might suggest that I have an intermediate interface module so that
>all screen i/o is done in one place. This is fine, except that I now have the
>same problem with that module instead.

:)

Don't be so sure. Look at the Borg pattern at the Activestate Python
Cookbook, or any implementation of the Singleton pattern.

>For some of my own modules (i.e. not curses) I could instantiate a class at
>the top level and pass it to everything which needs it. The problem with
>this is that the class is unknown at parse time, so errors wouldn't be found
>  until a piece of code was actually used. This might be months later for some
>obscure bits.

This is always a problem with a dynamically typed language like Python. I
don't see how you can avoid that by avoiding parameter passing. Or do you
mean  that you don't want to set a global variable in a module like I did
above? I can understand that you don't want that. It make themodule which
expects an importer to set things up difficult to use and understand.

But I don't think you can escape the need to test your code in such a way
that things like this are found during development. There might be *any*
fault in a piece of code, related to logical errors as well as to imports.
Deploying them and just hoping that they will work untested a few months
from now is not a good idea. This is why we write test programs to test our
programs.

>Having a basic moduile which is needed by many others must be a common
>problem. How is it normally handled?

Sure, and as I said, this problem exists for many objects, not
just modules.

Alex Martelli's Borg pattern looks like this.

class Borg:
     __shared_state = {}
     def __init__(self):
         self.__dict__ = self.__shared_state
     # and whatever else you want in your class -- that's all!

You could imagine something like this:
 >>> class InitOnce:
     __shared_state = {}
     def __init__(self):
         self.__dict__ = self.__shared_state
         if not self.__dict__:
             print "First Call, initialize"
             self.init = 1
         else:
             print "I'm already initialized, not need to repeat"

 >>> x1=InitOnce()
First Call, initialize
 >>> x2=InitOnce()
I'm already initialized, not need to repeat

Or with curses it might be something like:

class CursesWrapper:
     __shared_state = {}
     def __init__(sef):
         self.__dict__ = self.__shared_state
         if not self.__dict__:
             import curses
             self.curses = curses
             curses.initscr()

(I never used curses, but I hope you understand how to apply this)

I guess you might be able to replace

     import curses

with

     import curseswrapper
     cw = curseswrapper.CursesWrapper().curses

I know, it's a bit long and repetetive, but I'm sure you can adapt it.

Naturally, you don't need to call initscr in __init__. You can have a
CursesWrapper.initscr() like this:
class CursesWrapper:
     __shared_state = {}
     def __init__(sef):
         self.__dict__ = self.__shared_state
         if not self.__dict__:
             import curses
             self.curses = curses
             self._init = 0
     def initscr(self, *args, **kwargs):
         if not self._init:
             self.curses.initscr(*args, **kwargs)


--
Magnus Lycka (It's really Lyck&aring;), magnus at thinkware.se
Thinkware AB, Sweden, www.thinkware.se
I code Python ~ The Agile Programming Language 




More information about the Tutor mailing list