reSTify PEP 340 (#321)
This commit is contained in:
parent
75b53a7813
commit
f9865e18fd
141
pep-0340.txt
141
pep-0340.txt
|
@ -5,11 +5,12 @@ Last-Modified: $Date$
|
|||
Author: Guido van Rossum
|
||||
Status: Rejected
|
||||
Type: Standards Track
|
||||
Content-Type: text/plain
|
||||
Content-Type: text/x-rst
|
||||
Created: 27-Apr-2005
|
||||
Post-History:
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
This PEP proposes a new type of compound statement which can be
|
||||
used for resource management purposes. The new statement type
|
||||
|
@ -34,11 +35,13 @@ Introduction
|
|||
Bethard's help I have moved it to a separate PEP.)
|
||||
|
||||
Rejection Notice
|
||||
================
|
||||
|
||||
I am rejecting this PEP in favor of PEP 343. See the motivational
|
||||
section in that PEP for the reasoning behind this rejection. GvR.
|
||||
|
||||
Motivation and Summary
|
||||
======================
|
||||
|
||||
(Thanks to Shane Hathaway -- Hi Shane!)
|
||||
|
||||
|
@ -81,26 +84,29 @@ Motivation and Summary
|
|||
finishes, the interpreter leaves the block statement.
|
||||
|
||||
Use Cases
|
||||
=========
|
||||
|
||||
See the Examples section near the end.
|
||||
|
||||
Specification: the __exit__() Method
|
||||
====================================
|
||||
|
||||
An optional new method for iterators is proposed, called
|
||||
__exit__(). It takes up to three arguments which correspond to
|
||||
``__exit__()``. It takes up to three arguments which correspond to
|
||||
the three "arguments" to the raise-statement: type, value, and
|
||||
traceback. If all three arguments are None, sys.exc_info() may be
|
||||
traceback. If all three arguments are ``None``, ``sys.exc_info()`` may be
|
||||
consulted to provide suitable default values.
|
||||
|
||||
Specification: the Anonymous Block Statement
|
||||
============================================
|
||||
|
||||
A new statement is proposed with the syntax
|
||||
A new statement is proposed with the syntax::
|
||||
|
||||
block EXPR1 as VAR1:
|
||||
BLOCK1
|
||||
|
||||
Here, 'block' and 'as' are new keywords; EXPR1 is an arbitrary
|
||||
expression (but not an expression-list) and VAR1 is an arbitrary
|
||||
Here, 'block' and 'as' are new keywords; ``EXPR1`` is an arbitrary
|
||||
expression (but not an expression-list) and ``VAR1`` is an arbitrary
|
||||
assignment target (which may be a comma-separated list).
|
||||
|
||||
The "as VAR1" part is optional; if omitted, the assignments to
|
||||
|
@ -112,7 +118,7 @@ Specification: the Anonymous Block Statement
|
|||
all (which I actually like). PEP 310 uses 'with' for similar
|
||||
semantics, but I would like to reserve that for a with-statement
|
||||
similar to the one found in Pascal and VB. (Though I just found
|
||||
that the C# designers don't like 'with' [2], and I have to agree
|
||||
that the C# designers don't like 'with' [2]_, and I have to agree
|
||||
with their reasoning.) To sidestep this issue momentarily I'm
|
||||
using 'block' until we can agree on the right keyword, if any.
|
||||
|
||||
|
@ -121,15 +127,15 @@ Specification: the Anonymous Block Statement
|
|||
|
||||
Note that it is up to the iterator to decide whether a
|
||||
block-statement represents a loop with multiple iterations; in the
|
||||
most common use case BLOCK1 is executed exactly once. To the
|
||||
most common use case ``BLOCK1`` is executed exactly once. To the
|
||||
parser, however, it is always a loop; break and continue return
|
||||
transfer to the block's iterator (see below for details).
|
||||
|
||||
The translation is subtly different from a for-loop: iter() is
|
||||
not called, so EXPR1 should already be an iterator (not just an
|
||||
The translation is subtly different from a for-loop: ``iter()`` is
|
||||
not called, so ``EXPR1`` should already be an iterator (not just an
|
||||
iterable); and the iterator is guaranteed to be notified when
|
||||
the block-statement is left, regardless if this is due to a
|
||||
break, return or exception:
|
||||
break, return or exception::
|
||||
|
||||
itr = EXPR1 # The iterator
|
||||
ret = False # True if a return statement is active
|
||||
|
@ -159,15 +165,15 @@ Specification: the Anonymous Block Statement
|
|||
(However, the variables 'itr' etc. are not user-visible and the
|
||||
built-in names used cannot be overridden by the user.)
|
||||
|
||||
Inside BLOCK1, the following special translations apply:
|
||||
Inside ``BLOCK1``, the following special translations apply:
|
||||
|
||||
- "break" is always legal; it is translated into:
|
||||
- "break" is always legal; it is translated into::
|
||||
|
||||
exc = (StopIteration, None, None)
|
||||
continue
|
||||
|
||||
- "return EXPR3" is only legal when the block-statement is
|
||||
contained in a function definition; it is translated into:
|
||||
contained in a function definition; it is translated into::
|
||||
|
||||
exc = (StopIteration, None, None)
|
||||
ret = True
|
||||
|
@ -177,19 +183,19 @@ Specification: the Anonymous Block Statement
|
|||
The net effect is that break and return behave much the same as
|
||||
if the block-statement were a for-loop, except that the iterator
|
||||
gets a chance at resource cleanup before the block-statement is
|
||||
left, through the optional __exit__() method. The iterator also
|
||||
left, through the optional ``__exit__()`` method. The iterator also
|
||||
gets a chance if the block-statement is left through raising an
|
||||
exception. If the iterator doesn't have an __exit__() method,
|
||||
exception. If the iterator doesn't have an ``__exit__()`` method,
|
||||
there is no difference with a for-loop (except that a for-loop
|
||||
calls iter() on EXPR1).
|
||||
calls ``iter()`` on ``EXPR1``).
|
||||
|
||||
Note that a yield-statement in a block-statement is not treated
|
||||
differently. It suspends the function containing the block
|
||||
*without* notifying the block's iterator. The block's iterator is
|
||||
**without** notifying the block's iterator. The block's iterator is
|
||||
entirely unaware of this yield, since the local control flow
|
||||
doesn't actually leave the block. In other words, it is *not*
|
||||
doesn't actually leave the block. In other words, it is **not**
|
||||
like a break or return statement. When the loop that was resumed
|
||||
by the yield calls next(), the block is resumed right after the
|
||||
by the yield calls ``next()``, the block is resumed right after the
|
||||
yield. (See example 7 below.) The generator finalization
|
||||
semantics described below guarantee (within the limitations of all
|
||||
finalization semantics) that the block will be resumed eventually.
|
||||
|
@ -197,45 +203,47 @@ Specification: the Anonymous Block Statement
|
|||
Unlike the for-loop, the block-statement does not have an
|
||||
else-clause. I think it would be confusing, and emphasize the
|
||||
"loopiness" of the block-statement, while I want to emphasize its
|
||||
*difference* from a for-loop. In addition, there are several
|
||||
**difference** from a for-loop. In addition, there are several
|
||||
possible semantics for an else-clause, and only a very weak use
|
||||
case.
|
||||
|
||||
Specification: Generator Exit Handling
|
||||
======================================
|
||||
|
||||
Generators will implement the new __exit__() method API.
|
||||
Generators will implement the new ``__exit__()`` method API.
|
||||
|
||||
Generators will be allowed to have a yield statement inside a
|
||||
Generators will be allowed to have a ``yield`` statement inside a
|
||||
try-finally statement.
|
||||
|
||||
The expression argument to the yield-statement will become
|
||||
optional (defaulting to None).
|
||||
|
||||
When __exit__() is called, the generator is resumed but at the
|
||||
When ``__exit__()`` is called, the generator is resumed but at the
|
||||
point of the yield-statement the exception represented by the
|
||||
__exit__ argument(s) is raised. The generator may re-raise this
|
||||
``__exit__`` argument(s) is raised. The generator may re-raise this
|
||||
exception, raise another exception, or yield another value,
|
||||
except that if the exception passed in to __exit__() was
|
||||
except that if the exception passed in to ``__exit__()`` was
|
||||
StopIteration, it ought to raise StopIteration (otherwise the
|
||||
effect would be that a break is turned into continue, which is
|
||||
unexpected at least). When the *initial* call resuming the
|
||||
generator is an __exit__() call instead of a next() call, the
|
||||
unexpected at least). When the **initial** call resuming the
|
||||
generator is an ``__exit__()`` call instead of a ``next()`` call, the
|
||||
generator's execution is aborted and the exception is re-raised
|
||||
without passing control to the generator's body.
|
||||
|
||||
When a generator that has not yet terminated is garbage-collected
|
||||
(either through reference counting or by the cyclical garbage
|
||||
collector), its __exit__() method is called once with
|
||||
collector), its ``__exit__()`` method is called once with
|
||||
StopIteration as its first argument. Together with the
|
||||
requirement that a generator ought to raise StopIteration when
|
||||
__exit__() is called with StopIteration, this guarantees the
|
||||
``__exit__()`` is called with StopIteration, this guarantees the
|
||||
eventual activation of any finally-clauses that were active when
|
||||
the generator was last suspended. Of course, under certain
|
||||
circumstances the generator may never be garbage-collected. This
|
||||
is no different than the guarantees that are made about finalizers
|
||||
(__del__() methods) of other objects.
|
||||
(``__del__()`` methods) of other objects.
|
||||
|
||||
Alternatives Considered and Rejected
|
||||
====================================
|
||||
|
||||
- Many alternatives have been proposed for 'block'. I haven't
|
||||
seen a proposal for another keyword that I like better than
|
||||
|
@ -244,14 +252,14 @@ Alternatives Considered and Rejected
|
|||
Perhaps 'with' is the best choice after all?
|
||||
|
||||
- Instead of trying to pick the ideal keyword, the block-statement
|
||||
could simply have the form:
|
||||
could simply have the form::
|
||||
|
||||
EXPR1 as VAR1:
|
||||
BLOCK1
|
||||
|
||||
This is at first attractive because, together with a good choice
|
||||
of function names (like those in the Examples section below)
|
||||
used in EXPR1, it reads well, and feels like a "user-defined
|
||||
used in ``EXPR1``, it reads well, and feels like a "user-defined
|
||||
statement". And yet, it makes me (and many others)
|
||||
uncomfortable; without a keyword the syntax is very "bland",
|
||||
difficult to look up in a manual (remember that 'as' is
|
||||
|
@ -268,19 +276,20 @@ Alternatives Considered and Rejected
|
|||
|
||||
- This keeps getting proposed: "block VAR1 = EXPR1" instead of
|
||||
"block EXPR1 as VAR1". That would be very misleading, since
|
||||
VAR1 does *not* get assigned the value of EXPR1; EXPR1 results
|
||||
VAR1 does **not** get assigned the value of EXPR1; EXPR1 results
|
||||
in a generator which is assigned to an internal variable, and
|
||||
VAR1 is the value returned by successive calls to the __next__()
|
||||
VAR1 is the value returned by successive calls to the ``__next__()``
|
||||
method of that iterator.
|
||||
|
||||
- Why not change the translation to apply iter(EXPR1)? All the
|
||||
- Why not change the translation to apply ``iter(EXPR1)``? All the
|
||||
examples would continue to work. But this makes the
|
||||
block-statement *more* like a for-loop, while the emphasis ought
|
||||
to be on the *difference* between the two. Not calling iter()
|
||||
block-statement **more** like a for-loop, while the emphasis ought
|
||||
to be on the **difference** between the two. Not calling ``iter()``
|
||||
catches a bunch of misunderstandings, like using a sequence as
|
||||
EXPR1.
|
||||
``EXPR1``.
|
||||
|
||||
Comparison to Thunks
|
||||
====================
|
||||
|
||||
Alternative semantics proposed for the block-statement turn the
|
||||
block into a thunk (an anonymous function that blends into the
|
||||
|
@ -306,7 +315,7 @@ Comparison to Thunks
|
|||
not about to go there :-).
|
||||
|
||||
But then an IMO important use case for the resource cleanup
|
||||
template pattern is lost. I routinely write code like this:
|
||||
template pattern is lost. I routinely write code like this::
|
||||
|
||||
def findSomething(self, key, default=None):
|
||||
self.lock.acquire()
|
||||
|
@ -318,7 +327,7 @@ Comparison to Thunks
|
|||
finally:
|
||||
self.lock.release()
|
||||
|
||||
and I'd be bummed if I couldn't write this as:
|
||||
and I'd be bummed if I couldn't write this as::
|
||||
|
||||
def findSomething(self, key, default=None):
|
||||
block locking(self.lock):
|
||||
|
@ -327,7 +336,7 @@ Comparison to Thunks
|
|||
return item
|
||||
return default
|
||||
|
||||
This particular example can be rewritten using a break:
|
||||
This particular example can be rewritten using a break::
|
||||
|
||||
def findSomething(self, key, default=None):
|
||||
block locking(self.lock):
|
||||
|
@ -356,12 +365,12 @@ Comparison to Thunks
|
|||
or set in the thunk would have to become a 'cell' (our mechanism
|
||||
for sharing variables between nested scopes). Cells slow down
|
||||
access compared to regular local variables: access involves an
|
||||
extra C function call (PyCell_Get() or PyCell_Set()).
|
||||
extra C function call (``PyCell_Get()`` or ``PyCell_Set()``).
|
||||
|
||||
Perhaps not entirely coincidentally, the last example above
|
||||
(findSomething() rewritten to avoid a return inside the block)
|
||||
(``findSomething()`` rewritten to avoid a return inside the block)
|
||||
shows that, unlike for regular nested functions, we'll want
|
||||
variables *assigned to* by the thunk also to be shared with the
|
||||
variables **assigned to** by the thunk also to be shared with the
|
||||
containing function, even if they are not assigned to outside the
|
||||
thunk.
|
||||
|
||||
|
@ -372,7 +381,7 @@ Comparison to Thunks
|
|||
I believe there are definitely uses for this; several people have
|
||||
already shown how to do asynchronous light-weight threads using
|
||||
generators (e.g. David Mertz quoted in PEP 288, and Fredrik
|
||||
Lundh[3]).
|
||||
Lundh [3]_).
|
||||
|
||||
And finally, Greg says: "a thunk implementation has the potential
|
||||
to easily handle multiple block arguments, if a suitable syntax
|
||||
|
@ -387,12 +396,13 @@ Comparison to Thunks
|
|||
that defeats the purpose of using thunks in the first place.)
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
(Several of these examples contain "yield None". If PEP 342 is
|
||||
accepted, these can be changed to just "yield" of course.)
|
||||
|
||||
1. A template for ensuring that a lock, acquired at the start of a
|
||||
block, is released when the block is left:
|
||||
block, is released when the block is left::
|
||||
|
||||
def locking(lock):
|
||||
lock.acquire()
|
||||
|
@ -401,7 +411,7 @@ Examples
|
|||
finally:
|
||||
lock.release()
|
||||
|
||||
Used as follows:
|
||||
Used as follows::
|
||||
|
||||
block locking(myLock):
|
||||
# Code here executes with myLock held. The lock is
|
||||
|
@ -409,7 +419,7 @@ Examples
|
|||
# if via return or by an uncaught exception).
|
||||
|
||||
2. A template for opening a file that ensures the file is closed
|
||||
when the block is left:
|
||||
when the block is left::
|
||||
|
||||
def opening(filename, mode="r"):
|
||||
f = open(filename, mode)
|
||||
|
@ -418,14 +428,14 @@ Examples
|
|||
finally:
|
||||
f.close()
|
||||
|
||||
Used as follows:
|
||||
Used as follows::
|
||||
|
||||
block opening("/etc/passwd") as f:
|
||||
for line in f:
|
||||
print line.rstrip()
|
||||
|
||||
3. A template for committing or rolling back a database
|
||||
transaction:
|
||||
transaction::
|
||||
|
||||
def transactional(db):
|
||||
try:
|
||||
|
@ -436,7 +446,7 @@ Examples
|
|||
else:
|
||||
db.commit()
|
||||
|
||||
4. A template that tries something up to n times:
|
||||
4. A template that tries something up to n times::
|
||||
|
||||
def auto_retry(n=3, exc=Exception):
|
||||
for i in range(n):
|
||||
|
@ -448,20 +458,20 @@ Examples
|
|||
continue
|
||||
raise # re-raise the exception we caught earlier
|
||||
|
||||
Used as follows:
|
||||
Used as follows::
|
||||
|
||||
block auto_retry(3, IOError):
|
||||
f = urllib.urlopen("http://www.python.org/dev/peps/pep-0340/")
|
||||
print f.read()
|
||||
|
||||
5. It is possible to nest blocks and combine templates:
|
||||
5. It is possible to nest blocks and combine templates::
|
||||
|
||||
def locking_opening(lock, filename, mode="r"):
|
||||
block locking(lock):
|
||||
block opening(filename) as f:
|
||||
yield f
|
||||
|
||||
Used as follows:
|
||||
Used as follows::
|
||||
|
||||
block locking_opening(myLock, "/etc/passwd") as f:
|
||||
for line in f:
|
||||
|
@ -470,10 +480,10 @@ Examples
|
|||
(If this example confuses you, consider that it is equivalent
|
||||
to using a for-loop with a yield in its body in a regular
|
||||
generator which is invoking another iterator or generator
|
||||
recursively; see for example the source code for os.walk().)
|
||||
recursively; see for example the source code for ``os.walk()``.)
|
||||
|
||||
6. It is possible to write a regular iterator with the
|
||||
semantics of example 1:
|
||||
semantics of example 1::
|
||||
|
||||
class locking:
|
||||
def __init__(self, lock):
|
||||
|
@ -500,7 +510,7 @@ Examples
|
|||
examples; it shows how much simpler generators are for the same
|
||||
purpose.)
|
||||
|
||||
7. Redirect stdout temporarily:
|
||||
7. Redirect stdout temporarily::
|
||||
|
||||
def redirecting_stdout(new_stdout):
|
||||
save_stdout = sys.stdout
|
||||
|
@ -510,13 +520,13 @@ Examples
|
|||
finally:
|
||||
sys.stdout = save_stdout
|
||||
|
||||
Used as follows:
|
||||
Used as follows::
|
||||
|
||||
block opening(filename, "w") as f:
|
||||
block redirecting_stdout(f):
|
||||
print "Hello world"
|
||||
|
||||
8. A variant on opening() that also returns an error condition:
|
||||
8. A variant on ``opening()`` that also returns an error condition::
|
||||
|
||||
def opening_w_error(filename, mode="r"):
|
||||
try:
|
||||
|
@ -529,7 +539,7 @@ Examples
|
|||
finally:
|
||||
f.close()
|
||||
|
||||
Used as follows:
|
||||
Used as follows::
|
||||
|
||||
block opening_w_error("/etc/passwd", "a") as f, err:
|
||||
if err:
|
||||
|
@ -538,6 +548,7 @@ Examples
|
|||
f.write("guido::0:0::/:/bin/sh\n")
|
||||
|
||||
Acknowledgements
|
||||
================
|
||||
|
||||
In no useful order: Alex Martelli, Barry Warsaw, Bob Ippolito,
|
||||
Brett Cannon, Brian Sabbey, Chris Ryland, Doug Landauer, Duncan
|
||||
|
@ -550,13 +561,15 @@ Acknowledgements
|
|||
contributions!
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
[1] https://mail.python.org/pipermail/python-dev/2005-April/052821.html
|
||||
.. [1] https://mail.python.org/pipermail/python-dev/2005-April/052821.html
|
||||
|
||||
[2] http://msdn.microsoft.com/vcsharp/programming/language/ask/withstatement/
|
||||
.. [2] http://msdn.microsoft.com/vcsharp/programming/language/ask/withstatement/
|
||||
|
||||
[3] http://effbot.org/zone/asyncore-generators.htm
|
||||
.. [3] http://effbot.org/zone/asyncore-generators.htm
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
|
|
Loading…
Reference in New Issue