[Python-Dev] Merging PEP 310 and PEP 340-redux?

Guido van Rossum gvanrossum at gmail.com
Fri May 13 12:05:20 CEST 2005


I just read Raymond Chen's rant against control flow macros:
http://blogs.msdn.com/oldnewthing/archive/2005/01/06/347666.aspx

I think this pretty much kills PEP 340, as well as Nick Coghlan's
alternative: both proposals let you write a "template" that can be
used to hide exception-catching code, which is a form of control flow
(and a pretty important one if you read Chen's rant against exceptions
referenced by the former, even if you don't agree with everything he
says in the latter).

Which leaves us, IMO, with the choice between PEP 310 and my own
"PEP-340-redux" proposal; these *only* introduce a finally-clause,
which does not affect the control flow. I'm not counting exceptions
that might happen in the finally-clause; exceptions can happen
anywhere anyway. But I am counting the *catching* of an exception as
control flow, since that means that code past BLOCK (in the same
function) is reachable even if BLOCK was not executed to completion;
and this is the argument against PEP 340 and against Nick's
alternative.

Let's compare and contrast the two remaining competitors:

PEP 310
=======

Syntax:
with EXPR [= VAR]:
    BLOCK

Translation:
[VAR =] abc = EXPR
if hasattr(abc, "__enter__"):
    abc.__enter__()
try:
    BLOCK
finally:
    abc.__exit__()

Pros:
- dead simple

Cons:
- can't use a decorated generator for EXPR

PEP 340 redux
=============

Syntax:
do EXPR [as VAR]:
    BLOCK

Translation:
abc = EXPR
[VAR =] abc.__enter__()
try:
    BLOCK
finally:
    abc.__exit__(*"sys.exc_info()") # Not exactly

Pros:
- can use a decorated generator as EXPR
- separation of EXPR and VAR (VAR gets what EXPR.__enter__() returns)

Cons:
- slightly less simple (__enter__ must return something for VAR;
  __exit__ takes optional args)

Everything else is equal or can be made equal. We can make them more
equal by treating the arguments passed to __exit__() as a separate
decision, and waffling about whether __enter__() should be optional (I
think it's a bad idea even for PEP 310; it *could* be made optional
for PEP 340 redux).

Let's also not quibble about the keyword used; again, that can be a
separate decision. Note that only PEP 310 can use the "VAR = EXPR"
syntax; PEP 340 redux *must* use "EXPR as VAR" since it doesn't assign
the value of EXPR to VAR; PEP 310 can be rewritten using this syntax
as well.

So then the all-important question I want to pose is: do we like the
idea of using a (degenerate, decorated) generator as a "template" for
the do-statement enough to accept the slightly increased complexity?
The added complexity is caused by the need to separate VAR from EXPR
so that a generator can be used. I personally like this separation; I
actually like that the "anonymous block controller" is logically
separate from the variable bound by the construct. From Greg Ewing's
response to the proposal to endow file objects with __enter__ and
__exit__ methods, I believe he thinks so too.

Straight up-or-down votes in the full senate are appreciated at this point.

On to the secondary questions:

- Today I like the 'do' keyword better; 'with' might confuse folks
coming from Pascal or VB

- I have a more elaborate proposal for __exit__'s arguments. Let the
translation be as follows:

abc = EXPR
[VAR =] abc.__enter__()
oke = False  # Pronounced "okay"
exc = ()
try:
    try:
        BLOCK
        oke = True
    except:
        exc = sys.exc_info()
        raise
finally:
    abc.__exit__(oke, *exc)

This means that __exit__ can be called with the following arguments:

abc.__exit__(True) - normal completion of BLOCK

abc.__exit__(False) - BLOCK was left by a non-local goto (break/continue/return)

abc.__exit__(False, t, v, tb) - BLOCK was left by an exception

(An alternative would be to always call it with 4 arguments, the last
three being None in the first two cases.)

If we adopt PEP 340 redux, it's up to the decorator for degenerate
generators to decide how to pass this information into the generator;
if we adopt PEP 342 ("continue EXPR") at the same time, we can let the
yield-expression return a 4-tuple (oke, t, v, tb). Most templates can
ignore this information (so they can just use a yield-statement).

PS. I've come up with another interesting use case: block signals for
the duration of a block. This could be a function in the signal
module, e.g. signal.blocking([ist of signals to block]). The list
would default to all signals. Similar signal.ignoring().

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


More information about the Python-Dev mailing list