[Python-Dev] Garbage collecting closures

Paul Prescod paul@prescod.net
Mon, 14 Apr 2003 12:32:06 -0700


Jeremy Hylton wrote:
 > The details of when finalizers are called is an implementation detail
 > rather than a language property.

and

Guido van Rossum wrote:
> ... Since then,
> the rule has always been "some arbitrary time after nothing refers to
> them."  And the corollary is "always explicitly close your external
> resources."

I knew I'd hear that. ;) Overall, I agree. Anyhow, I'll give you some 
background so you can understand my use case. Then you can decide for 
yourself whether it is worth supporting.

When you're dealing with COM objects, you do stuff like:

  foo = a.b.c.d

b, c and d are all temporary, reference counted objects: reference 
counted on both the COM and Python sides. It is quite inconvenient to 
treat them as "resources" like database handles or something.


a = Dispatch("xxx.yyy")

b = a.b
c = b.c
d = c.d

a.release()
b.release()
c.release()

80% of the variables in my code are COM objects!

I'm not a big win32com programmer, but it is my impression that this is 
NOT the typical programming style.

COM is specifically designed to use reference counting so that 
programmers (even C++ programmers!) don't have to do explicit 
deallocation. COM and CPython have roughly the same garbage collection 
model (reference counted) so there is no need to treat them as special 
external resources. (nowadays, Python cleans up circular references and 
COM doesn't, so there is a minor divergence there)

The truth is that even after having been bitten, I'd rather deal with 
the 3 or 4 exceptional garbage collection cases (circular references 
with finalizers, closures, etc.) than uglify and complicate my Python 
code! I'll explicitly run GC in a shutdown() method.

Even though it is easy to work around, this particular special case 
really feels pathological to me. Simple transformations set it off, and 
they can be quite non-local. From:

Safe:

def a():
     if something:
         a()

def b():
     a()
     ... # ten thousand lines of code
     x = com_object

to

Buggy:

def b():
     def a():
         if something:
             a()
     a()
     ... # ten thousand lines of code
     x = com_object

OR

Safe:

def b():
     def a():
         if something:
             a()
     a()
     ... # ten thousand lines of code
     com_object.do_something()

to

Buggy:

def b():
     def a():
         if something:
             a()
     a()
     # ten thousand lines of code
     junk = com_object.do_something()

If I'm the first and last person to have this problem, then I guess it 
won't be a big deal, but it sure was confusing for me to debug. The 
containing app wouldn't shut down while Python owned a reference.

  Paul Prescod