Add a motivational section, remove tabs, add colons, and some very
minor edits.
This commit is contained in:
parent
3df485114c
commit
33cff6d943
114
pep-0343.txt
114
pep-0343.txt
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue