[Python-3000] Removing __del__

Michael Chermside mcherm at mcherm.com
Wed Sep 20 18:27:56 CEST 2006


Giovanni Bajo writes:
> I believe my example is a good use case for __del__ with no good
> enough workaround, which was requested by Micheal in the original post. I
> believe that it would be a mistake to remove __del__ unless we provide a
> graceful alternative (and I don't consider the code above a graceful
> alternative). I still like the __close__ method being proposed.

Thank you!

This is exactly the kind of discussion that I was hoping to engender.
Let me see if I can make the case a little more effectively. First of
all, let clean up Jean-Paul's solution a little bit so it looks prettier
when used. Let's put the following code into a module:

----- deletions.py -----
import weakref

# Maintain a separate list so the weakrefs themselves
# won't be garbage collected.
on_del_callbacks = []


def on_del_invoke(obj, func, *args, **kwargs):
     """This sets up a callback to be executed when an object
     is finalized. It is similar to the old __del__ method but
     without some of the risks and limitations of that method.

     The first argument is an object to watch; the second is a
     callable. After the object being watched gets finalized,
     the callable will be invoked; arguments for this call can
     be provided after the callable.

     Please note that the callable must not be a bound method
     of the object being watched, and the object being watched
     must not be (or be refered to by) one of the arguments
     or else the object will never be garbage collected."""

     def callback(ref):
         on_del_callbacks.remove(ref)
         func(*args, **kwargs)

     on_del_callbacks.append(
         weakref.ref(obj, callback))
--- end deletions.py ---

Performance could be improved in minor ways (avoiding the O(n)
lookup cost in the remove() call; avoiding the need for a
separate function object for each callback; catching obvious
loops and raising an exception immediately to make it more
newbie-friendly), but this will do for discussion.

Using this, your original code:

> class Wrapper:
>     def __init__(self, *args):
>          self.handle = CAPI.init(*args)
>
>     def __del__(self, *args):
>           CAPI.close(self.handle)
>
>     def foo(self):
>           CAPI.foo(self.handle)

becomes this code:

   from deletions import on_del_invoke

   class Wrapper:
       def __init__(self, *args):
           self.handle = CAPI.init(*args)
           on_del_invoke(self, CAPI.close, self.handle)

       def foo(self):
           CAPI.foo(self.handle)

It's actually *fewer* lines this way, and I find it quite
readable. Furthermore, unlike the __del__ version it doesn't
break as soon as someone accidentally puts a Wrapper object
into a loop.

Working from this example, I'm not convinced that the price
of giving up __del__ is really all that high. (But please,
find another example to convince me!)

On the other side of the scales, here are some benefits that
we gain if we get rid of __del__:

   * Simpler GC code which is less likely to have obscure
     bugs that are incredibly difficult to track down. Less
     core developer time spent maintaining complex, fragile
     code.

   * No need to explain about keeping __del__ objects[1] out
     of reference loops. In exchange, we choose explain
     about not passing the object being monitored or
     anything that links to it as arguments to on_del_invoke.
     I find that preferable because: (1) it seems more
     intuitive to me that the callback musn't reference the
     object being finalized, (2) it requires reasoning about
     the call-site, not about all future uses of the object,
     and (3) if the programmer violates this rule then the
     disadvantage is that the objects become immortal -- which
     is true for ALL __del__ objects in loops today.

   * Programmers no longer have the ability to allow __del__
     to resurect the object being finalized. Technically,
     that's a disadvantage, not an advantage, but I honestly
     don't think anyone believes it's a good idea to write
     __del__ methods that resurect the object, so I'm happy
     to lose that ability.

-- Michael Chermside


[1] - I'm using "__del__ object" to mean an object that has
    a __del__ method.



More information about the Python-3000 mailing list