new syntax for cleanup (was Re: destructors order not guaranteed?)

Jeff Petkau jpet at eskimo.com
Thu Nov 2 02:03:09 EST 2000


Alex Martelli <aleaxit at yahoo.com> wrote in message
news:8tpse1724oa at news2.newsguy.com...
> Harder to get used to, for experienced C++'ers: since no
> "stack-allocated, LIFO-lived, destructor execution is
> guaranteed even in the presence of exceptions" objects
> exist, you cannot (safely and portably) rely on __del__
> for general-purpose finalization operations -- it may work
...
> So, the beautiful C++ idiom...:
>
>     template <class Resource>
>     struct Locker {
>         Resource& pr;
>         void (*closer)(Resource&);
>         Locker(Resource& pr, void (*closer)(Resource&)):
>             pr(pr), closer(closer) {}
>         ~Locker() { closer(pr); }
>     };
>
> has no appropriate Python translation (nor Java, etc).

This is the one thing I really miss from C++ when I'm using
sensible languges.  There are lots of things that normally
get cleaned up in stack unwind order--locks, semaphores,
open files, fp state, APIs with push/pop semantics like
OpenGL, etc.--and it's surprisingly difficult to get this
right in Python.

Suppose we've got a simple case:

    oldfp = ieee.setfp(prec=24, round='even')
    try:
        context = opencontext("whatever")
        try:
            stuff()
            more_stuff()
        finally:
            context.release()
    finally:
        ieee.setfp(oldfp)

This is currently the correct way to handle cleaning up on
unwind.  Unfortunately it's deeply indented and hard to
read, so hardly anyone ever writes code like this.  Mostly
people rely on __del__, or just don't bother to handle all
cases correctly. (Your example illustrates the most common
form of this bug--if acquire(), getit(), or dropIt() throws,
cleanup code is either missed or called when it shouldn't be.)

>     void funz:
>         try:
>             lock1 = myDatabase.acquire()
>             lock2 = myResource.getit()
>             fonz()
>         finally:
>             dropIt(lock2)
>             dbRelease(lock1)

Here's how I'd like to see this written, adding new syntax
for a cleanup: block, and wishfully speculating about an
ieee module while I'm at it:

    oldfp = ieee.setfp(prec=24, round='even')
    cleanup:
        ieee.setfp(oldfp)

    context = opencontext("whatever")
    cleanup:
        context.release()

    stuff()
    more_stuff()

The idea is that this would compile into exactly the same
bytecode as the previous example.  The cleanup code is
right next to the init code, so it's much clearer whether
you've got it right. It is a new keyword, but it could be
made context-sensitive so as not to break old code.

We could do it without changing the parser by adding a
cleanup() function which does evil with the frame object:

    oldfp = ieee.setfp(prec=24, round='even')
    cleanup(oldfp)      # assumes oldfp() does the restore;
                        # otherwise need a lambda here
    context = opencontext("whatever")
    cleanup(context.release)

    stuff()
    more_stuff()

We'd have to add a field to frame objects for a list of
objects to call up at frame exit. I think this is uglier
than a cleanup: block, and it only allows cleanup at frame
exit instead of block exit, but it avoids the new keyword.
Either would nicely solve the problem.

--Jeff






More information about the Python-list mailing list