[Python-Dev] PEP 343 - Abstract Block Redux

Guido van Rossum gvanrossum at gmail.com
Mon May 16 16:33:28 CEST 2005


[Guido]
> > In rev 1.10 I moved the __enter__ call out of the
> > try-block again. Having it inside was insane: when __enter__ fails, it
> > should do its own cleanup rather than expecting __exit__ to clean up
> > after a partial __enter__.

[Ka-Ping Yee]
> No, it wasn't insane.  You had a good reason for putting it there.

I did some introspection, and it was definitely a temporary moment of
insanity: (1) I somehow forgot the obvious solution (that __enter__
should clean up its own mess if it doesn't make it to the finish
line); (2) once a generator raises an exception it cannot be resumed,
so the generator-based example I gave can't work. (I was too lazy to
write down the class-based example, which *can* be made to work of
course.)

> The question is what style of implementation you want to encourage.
> 
> If you put __enter__ inside, then you encourage idempotent __exit__,
> which makes resource objects easier to reuse.

But consider threading.RLock (a lock with the semantics of Java's
monitors). Its release() is *not* idempotent, so we couldn't use the
shortcut of making __enter__ and __exit__ methods of the lock itself.
(I think this shortcut may be important for locks because it reduces
the overhead of frequent locking -- no extra objects need to be
allocated.)

> If you put __enter__ outside, that allows the trivial case to be
> written a little more simply, but also makes it hard to reuse.

I skimmed your longer post about that but didn't find the conclusive
evidence that this is so; all I saw was a lot of facts ("you can
implement scenario X in these three ways) but no conclusion.

The real reason I put it inside the try was different: there's a race
condition in the VM where it can raise a KeyboardInterrupt after the
__enter__() call completes but before the try-suite is entered, and
then __exit__() is never called. Similarly, if __enter__() is written
in Python and wraps some other operation, it may complete that other
operation but get a KeyboardInterrupt (or other asynchronous
exception) before reaching the (explicit or implicit) return
statement. Ditto for generators and yield.

But I think this can be solved differently in the actual translation
(as opposed to the "translate-to-valid-pre-2.5-Python"); the call to
__exit__ can be implicit in a new opcode, and then we can at least
guarantee that the interpreter doesn't check for interrupts between a
successful __exit__ call and setting up the finally block.

This doesn't handle an  __enter__ written in Python not reaching its
return; but in the presence of interrupts I don't think it's possible
to write such code reliably anyway; it should be written using a "with
signal.blocking()" around the critical code including the return.

I don't want to manipulate signals directly in the VM; it's
platform-specific, expensive, rarely needed, and you never know
whether you aren't invoking some Python code that might do I/O, making
the entire thread uninterruptible for a long time.

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


More information about the Python-Dev mailing list