Add a motivational section, remove tabs, add colons, and some very

minor edits.
This commit is contained in:
Guido van Rossum 2005-05-14 05:02:28 +00:00
parent 3df485114c
commit 33cff6d943
1 changed files with 107 additions and 7 deletions

View File

@ -17,7 +17,103 @@ Introduction
Motivation and Summary
TBD.
PEP 340, Anonymous Block Statements, combined many powerful ideas:
using generators as block templates, adding exception handling and
finalization to generators, and more. Besides praise it received
a lot of opposition from people who didn't like the fact that it
was, under the covers, a (optential) looping construct. This
meant that break and continue in a block-statement would break or
continue the block-statement, even if it was used as a non-looping
resource management tool.
But the final blow came when I read Raymond Chen's rant about
flow-control macros[1]. Raymond argues convincingly that hiding
flow control in macros makes your code inscrutable, and I find
that his argument applies to Python as well as to C. I realized
that PEP 340 templates can hide all sorts of control flow; for
example, its example 4 (auto_retry()) catches exceptions and
repeats the block up to three times.
However, the with-statement of PEP 310 does *not* hide control
flow, in my view: while a finally-suite temporarily suspends the
control flow, in the end, the control flow resumes as if the
finally-suite wasn't there at all. Consider this:
with VAR = EXPR:
BLOCK1
BLOCK2
Here, just as if the first line was "if True" instead, we know
that if BLOCK1 completes without an exception, BLOCK2 will be
reached; and if BLOCK1 raises an exception or executes a
non-local goto (a break, continue or return), BLOCK2 is *not*
reached. The magic added by the with-statement at the end doesn't
affect this.
(You may ask, what if a bug in the __exit__ method causes an
exception? Then all is lost -- but this is no worse than with
other exceptions; the nature of exceptions is that they can happen
*anywhere*, and you just have to live with that. Even if you
write bug-free code, a KeyboardInterrupt exception can still cause
it to exit between any two virtual machine opcodes.)
This argument almost led me to endorse PEP 310, but I had one idea
left from the PEP 340 euphoria that I wasn't ready to drop: using
generators as "templates" for abstractions like acquiring and
releasing a lock or opening and closing a file is a powerful idea,
as can be seen by looking at the examples in that PEP.
Inspired by a counter-proposal to PEP 340 by Phillip Eby I tried
to create a decorator that would turn a suitable generator into an
object with the necessary __entry__ and __exit__ methods. Here I
ran into a snag: while it wasn't too hard for the locking example,
it was impossible to do this for the opening example. The idea
was to define the template like this:
@with_template
def opening(filename):
f = open(filename)
yield f
f.close()
and used it like this:
with f = opening(filename):
...read data from f...
The problem is that in PEP 310, the result of calling EXPR is
assigned directly to VAR, and then VAR's __exit__ method is called
upon exit from BLOCK1. But here, VAR clearly needs to receive the
opened file, and that would mean that __exit__ would have to be a
method on the file.
While this can be solved using a proxy class, this is awkward and
made me realize that a slightly different translation would make
writing the desired decorator a piece of cake: let VAR receive the
result from calling the __enter__ method, and save the value of
EXPR to call its __exit__ method later. Then the decorator can
return an instance of a wrapper class whose __enter__ method calls
the generator's next() method and returns whatever next() returns;
the wrapper instance's __exit__ method calls next() again but
expects it to raise StopIteration. (Details below in the section
Optional Generator Decorator.)
So now the final hurdle was that the PEP 310 syntax:
with VAR = EXPR:
BLOCK1
would be deceptive, since VAR does *not* receive the value of
EXPR. Given PEP 340, it was an easy step to:
with EXPR as VAR:
BLOCK1
or, using an alternate keyword that has been proposed a number of
times:
do EXPR as VAR:
BLOCK1
Use Cases
@ -25,7 +121,7 @@ Use Cases
Specification
A new statement is proposed with the syntax
A new statement is proposed with the syntax:
do EXPR as VAR:
BLOCK
@ -53,7 +149,7 @@ Specification
BLOCK
except:
exc = sys.exc_info()
raise
raise
finally:
abc.__exit__(exc)
@ -66,7 +162,7 @@ Specification
The calling convention for abc.__exit__() is: as follows. If the
finally-suite was reached through normal completion of BLOCK or
through a "non-local goto" (a break, continue or return statement
through a non-local goto (a break, continue or return statement
in BLOCK), abc.__exit__() is called without arguments (or perhaps
with three None arguments?). If the finally-suite was reached
through an exception raised in BLOCK, abc.__exit__() is called
@ -122,18 +218,18 @@ Other Optional Extensions
It would be possible to endow certain objects, like files,
sockets, and locks, with __enter__ and __exit__ methods so that
instead of writing
instead of writing:
do locking(myLock):
BLOCK
one could write simply
one could write simply:
do myLock:
BLOCK
I think we should be careful with this; it could lead to mistakes
like
like:
f = open(filename)
do f:
@ -264,6 +360,10 @@ Examples
as an exercise for the reader. (Mail it to me if you'd like to
see it here.)
References
[1] http://blogs.msdn.com/oldnewthing/archive/2005/01/06/347666.aspx
Copyright
This document has been placed in the public domain.