reSTify PEP 340 (#321)

This commit is contained in:
Huang Huang 2017-08-12 02:58:49 +08:00 committed by Brett Cannon
parent 75b53a7813
commit f9865e18fd
1 changed files with 379 additions and 366 deletions

View File

@ -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.