[Python-Dev] Confusing behaviour relating to module objects being GCed.

Guido van Rossum guido@python.org
Fri, 13 Jun 2003 11:30:51 -0400


> Anyway, I'm having a problem in a large framework relating to using custom
> __import__ and reload hooks.  I've been able to reduce the code that
> demonstrates this problem to this Python code:
> 
> Python 2.3b1 (#1, Apr 27 2003, 22:07:38)
> [GCC 2.95.3 20010315 (release)] on linux2
> Type "help", "copyright", "credits" or "license" for more information.
> >>> import new
> >>> mone = new.module('mone')
> >>> mone.mtwo = new.module('mtwo')
> >>> mone.mtwo.var = 42
> >>> exec "def func():\n  print var\n  delmtwo()\n  print var\n" in
> mone.mtwo.__dict__
> >>> def delmtwo():
> ...   delattr(mone, 'mtwo')
> ...
> >>> mone.mtwo.delmtwo = delmtwo
> >>> mone.mtwo.func()             # FIRST CASE
> 42
> None
> >>> mone.mtwo = new.module('mtwo')
> >>> mone.mtwo.var = 42
> >>> exec "def func():\n  print var\n  delmtwo()\n  print var\n" in
> mone.mtwo.__dict__
> >>> keepref = mone.mtwo
> >>> mone.mtwo.delmtwo = delmtwo
> >>> mone.mtwo.func()             # SECOND CASE
> 42
> 42
> 
> The problem is that after mone.mtwo.func() calls delmtwo() which
> removes the mtwo module from mone and thus reduces the ref-count to
> 0 (in the first case), func.im_func.func_globals starts showing a
> very peculiar behaviour.  Basically, all of the module-level
> variable *names* stay, but all the *values* become None.  As the
> second call to func() demonstrates, if I keep a ref to the module
> which is deleted from mone, everything is fine.
> 
> I am at a loss explaining this behaviour (not knowing anything about
> Python internals) and I would greatly appreciate enlightment that
> either confirms that this is a bug, or explains why it isn't :-)

The crucial part here is "not knowing anything about Python internals"
:-).  Please take some time to read the Python C source code.  If you
don't have it downloaded, you can view it online here:

  http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/python/python/dist/src/

Look at the destructor for module objects (file
Objects/moduleobject.c, function module_dealloc()).  This is what
destroys the module when its refcount goes to zero.  It calls
_PyModule_Clear() which carefully sets all the module's global
variables to None.  The reason this is done is hard to explain; it has
to do with trying to break cyclical references between a module and
the classes and functions it defines, in an attempt at destroying as
many objects as possible when Python exits, in the hope that this will
cause user-defined destructors (__del__ methods) to be callled, which
might do useful things like flushing buffers, removing temporary
files, and releasing other external resources.  (Closing files and
releasing memory are *not* goals here, since this happens anyway when
a process exits.)

--Guido van Rossum (home page: http://www.python.org/~guido/)