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
|
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
|
Use Cases
|
||||||
|
|
||||||
|
@ -25,7 +121,7 @@ Use Cases
|
||||||
|
|
||||||
Specification
|
Specification
|
||||||
|
|
||||||
A new statement is proposed with the syntax
|
A new statement is proposed with the syntax:
|
||||||
|
|
||||||
do EXPR as VAR:
|
do EXPR as VAR:
|
||||||
BLOCK
|
BLOCK
|
||||||
|
@ -53,7 +149,7 @@ Specification
|
||||||
BLOCK
|
BLOCK
|
||||||
except:
|
except:
|
||||||
exc = sys.exc_info()
|
exc = sys.exc_info()
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
abc.__exit__(exc)
|
abc.__exit__(exc)
|
||||||
|
|
||||||
|
@ -66,7 +162,7 @@ Specification
|
||||||
|
|
||||||
The calling convention for abc.__exit__() is: as follows. If the
|
The calling convention for abc.__exit__() is: as follows. If the
|
||||||
finally-suite was reached through normal completion of BLOCK or
|
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
|
in BLOCK), abc.__exit__() is called without arguments (or perhaps
|
||||||
with three None arguments?). If the finally-suite was reached
|
with three None arguments?). If the finally-suite was reached
|
||||||
through an exception raised in BLOCK, abc.__exit__() is called
|
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,
|
It would be possible to endow certain objects, like files,
|
||||||
sockets, and locks, with __enter__ and __exit__ methods so that
|
sockets, and locks, with __enter__ and __exit__ methods so that
|
||||||
instead of writing
|
instead of writing:
|
||||||
|
|
||||||
do locking(myLock):
|
do locking(myLock):
|
||||||
BLOCK
|
BLOCK
|
||||||
|
|
||||||
one could write simply
|
one could write simply:
|
||||||
|
|
||||||
do myLock:
|
do myLock:
|
||||||
BLOCK
|
BLOCK
|
||||||
|
|
||||||
I think we should be careful with this; it could lead to mistakes
|
I think we should be careful with this; it could lead to mistakes
|
||||||
like
|
like:
|
||||||
|
|
||||||
f = open(filename)
|
f = open(filename)
|
||||||
do f:
|
do f:
|
||||||
|
@ -264,6 +360,10 @@ Examples
|
||||||
as an exercise for the reader. (Mail it to me if you'd like to
|
as an exercise for the reader. (Mail it to me if you'd like to
|
||||||
see it here.)
|
see it here.)
|
||||||
|
|
||||||
|
References
|
||||||
|
|
||||||
|
[1] http://blogs.msdn.com/oldnewthing/archive/2005/01/06/347666.aspx
|
||||||
|
|
||||||
Copyright
|
Copyright
|
||||||
|
|
||||||
This document has been placed in the public domain.
|
This document has been placed in the public domain.
|
||||||
|
|
Loading…
Reference in New Issue