Initial draft.
This commit is contained in:
parent
8e190c714a
commit
81f1868248
|
@ -0,0 +1,308 @@
|
|||
PEP: 340
|
||||
Title: Anonymous Block Statements
|
||||
Version: $Revision$
|
||||
Last-Modified: $Date$
|
||||
Author: Guido van Rossum
|
||||
Status: Draft
|
||||
Type: Standards Track
|
||||
Content-Type: text/plain
|
||||
Created: 27-Apr-2005
|
||||
Post-History:
|
||||
|
||||
Introduction
|
||||
|
||||
This PEP proposes a new type of compound statement which can be
|
||||
used for resource management purposes, and a new iterator API to
|
||||
go with it. The new statement type is provisionally called the
|
||||
block-statement because the keyword to be used has not yet been
|
||||
chosen.
|
||||
|
||||
This PEP competes with several other PEPs: PEP 288 (Generators
|
||||
Attributes and Exceptions; only the second part), PEP 310
|
||||
(Reliable Acquisition/Release Pairs), and PEP 325
|
||||
(Resource-Release Support for Generators).
|
||||
|
||||
This proposal is just a strawman; we've had a heated debate about
|
||||
this on python-dev recently [1], and I figured it would be time to
|
||||
write up a precise spec in PEP form.
|
||||
|
||||
Motivation and Use Cases
|
||||
|
||||
TBD.
|
||||
|
||||
Specification: the Iteration Exception Hierarchy
|
||||
|
||||
Two new built-in exceptions are defined, and StopIteration is
|
||||
moved in the exception hierarchy:
|
||||
|
||||
class Iteration(Exception):
|
||||
pass
|
||||
|
||||
class StopIteration(Iteration):
|
||||
pass
|
||||
|
||||
class ContinueIteration(Iteration):
|
||||
def __init__(self, value=None):
|
||||
self.value = None
|
||||
|
||||
Specification: the __next__() Method
|
||||
|
||||
A new method for iterators is proposed, called __next__(). It
|
||||
takes one optional argument, which defaults to None. If not None,
|
||||
the argument must be an Iteration instance. Calling the
|
||||
__next__() method without argument or with None is equivalent to
|
||||
using the old iterator API, next(). For backwards compatibility,
|
||||
it is recommended that iterators also implement a next() method as
|
||||
an alias for calling the __next__() method without an argument.
|
||||
|
||||
Calling the __next__() method with a StopIteration instance
|
||||
signals the iterator that the caller wants to abort the iteration
|
||||
sequence; the iterator should respond by doing any necessary
|
||||
cleanup and raising StopIteration. Calling it with a
|
||||
ContinueIteration instance signals the iterator that the caller
|
||||
wants to continue the iteration; the ContinueIteration exception
|
||||
has a 'value' attribute which may be used by the iterator as a
|
||||
hint on what to do next. Calling it with a (base class) Iteration
|
||||
instance is the same as calling it with None.
|
||||
|
||||
Specification: the next() Built-in Function
|
||||
|
||||
This is a built-in function defined as follows:
|
||||
|
||||
def next(itr, arg=None):
|
||||
nxt = getattr(itr, "__next__", None)
|
||||
if nxt is not None:
|
||||
return nxt(arg)
|
||||
if arg is None:
|
||||
return itr.next()
|
||||
raise TypeError("next() with arg for old-style iterator")
|
||||
|
||||
Specification: the 'for' Loop
|
||||
|
||||
A small change in the translation of the for-loop is proposed.
|
||||
The statement
|
||||
|
||||
for VAR1 in EXPR1:
|
||||
BLOCK1
|
||||
else:
|
||||
BLOCK2
|
||||
|
||||
will be translated as follows:
|
||||
|
||||
itr = iter(EXPR1)
|
||||
arg = None
|
||||
while True:
|
||||
try:
|
||||
VAR1 = next(itr, arg)
|
||||
finally:
|
||||
break
|
||||
arg = None
|
||||
BLOCK1
|
||||
else:
|
||||
BLOCK2
|
||||
|
||||
(However, 'it' and 'arg' are hidden from the user, their scope
|
||||
ends when the while-loop is exited, and they are not shared with
|
||||
nested or outer for-loops, and the user cannot override the
|
||||
built-ins referenced.)
|
||||
|
||||
Specification: the Extended 'continue' Statement
|
||||
|
||||
In the translation of the for-loop, inside BLOCK1, the new syntax
|
||||
|
||||
continue EXPR2
|
||||
|
||||
is legal and is translated into
|
||||
|
||||
arg = ContinueIteration(EXPR2)
|
||||
continue
|
||||
|
||||
(Where 'arg' references the corresponding hidden variable from the
|
||||
previous section.)
|
||||
|
||||
This is also the case in the body of the block-statement proposed
|
||||
below.
|
||||
|
||||
Specification: the Anonymous Block Statement
|
||||
|
||||
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
|
||||
assignment target (which may be a comma-separated list).
|
||||
|
||||
The "as VAR1" part is optional; if omitted, the assignment to VAR1
|
||||
in the translation below is omitted (but the next() call is not!).
|
||||
|
||||
The choice of the 'block' keyword is contentious; it has even been
|
||||
proposed not to use a keyword at all. 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. To
|
||||
sidestep this issue momentarily I'm using 'block' until we can
|
||||
agree on a keyword. (I just found that the C# designers don't
|
||||
like 'with' [2].)
|
||||
|
||||
Note that it is left in the middle whether a block-statement
|
||||
represents a loop or not; this is up to the iterator, but in the
|
||||
most common case BLOCK1 is executed exactly once.
|
||||
|
||||
The translation is subtly different from the translation of 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 exhausted when the block-statement is left:
|
||||
|
||||
itr = EXPR1
|
||||
exc = arg = None
|
||||
ret = False
|
||||
while True:
|
||||
try:
|
||||
VAR1 = next(itr, arg)
|
||||
except StopIteration:
|
||||
if exc is not None:
|
||||
if ret:
|
||||
return exc
|
||||
else:
|
||||
raise exc # XXX See below
|
||||
break
|
||||
try:
|
||||
exc = arg = None
|
||||
BLOCK1
|
||||
except Exception, exc:
|
||||
arg = StopIteration()
|
||||
|
||||
(Again, 'it' etc. are hidden, and the user cannot override the
|
||||
built-ins.)
|
||||
|
||||
The "raise exc" translation is inexact; this is supposed to
|
||||
re-raise the exact exception that was raised inside BLOCK1, with
|
||||
the same traceback. We can't use a bare raise-statement because
|
||||
we've just caught StopIteration.
|
||||
|
||||
Inside BLOCK1, the following special translations apply:
|
||||
|
||||
- "continue" and "continue EXPR2" are always legal; the latter is
|
||||
translated as shown earlier:
|
||||
|
||||
arg = ContinueIteration(EXPR2)
|
||||
continue
|
||||
|
||||
- "break" is always legal; it is translated into:
|
||||
|
||||
arg = StopIteration()
|
||||
continue
|
||||
|
||||
- "return EXPR3" is only legal when the block-statement is
|
||||
contained in a function definition; it is translated into:
|
||||
|
||||
exc = EXPR3
|
||||
ret = True
|
||||
arg = StopIteration()
|
||||
continue
|
||||
|
||||
The net effect is that break, continue 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. The iterator also gets a chance if the
|
||||
block-statement is left through raising an exception.
|
||||
|
||||
Specification: Generator Exception Handling
|
||||
|
||||
Generators will implement the new __next__() method API, as well
|
||||
as the old argument-less next() method.
|
||||
|
||||
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).
|
||||
|
||||
The yield-statement will be allowed to be used on the right-hand
|
||||
side of an assignment; in that case it is referred to as
|
||||
yield-expression. The value of this yield-expression is None
|
||||
unless __next__() was called with a ContinueIteration argument;
|
||||
see below.
|
||||
|
||||
A yield-expression must always be parenthesized except when it
|
||||
occurs at the top-level expression on the right-hand side of an
|
||||
assignment. So
|
||||
|
||||
x = yield 42
|
||||
x = yield
|
||||
x = 12 + (yield 42)
|
||||
x = 12 + (yield)
|
||||
foo(yield 42)
|
||||
foo(yield)
|
||||
|
||||
are all legal, but
|
||||
|
||||
x = 12 + yield 42
|
||||
x = 12 + yield
|
||||
foo(yield 42, 12)
|
||||
foo(yield, 12)
|
||||
|
||||
are all illegal. (Some of the edge cases are motivated by the
|
||||
current legality of "yield 12, 42".)
|
||||
|
||||
When __next__() is called with a StopIteration instance argument,
|
||||
the yield statement that is resumed by the __next__() call will
|
||||
raise this StopIteration exception. The generator should re-raise
|
||||
this exception; it should not yield another value. When the
|
||||
*initial* call to __next__() receives a StopIteration instance
|
||||
argument, the generator's execution is aborted and the exception
|
||||
is re-raised without passing control to the generator's body.
|
||||
|
||||
When __next__() is called with a ContinueIteration instance
|
||||
argument, the yield-expression that it resumes will return the
|
||||
value attribute of the argument. If it resumes a yield-statement,
|
||||
the value is ignored. When the *initial* call to __next__()
|
||||
receives a ContinueIteration instance argument, the generator's
|
||||
execution is started normally; the argument's value attribute is
|
||||
ignored.
|
||||
|
||||
When a generator that has not yet terminated is garbage-collected
|
||||
(either through reference counting or by the cyclical garbage
|
||||
collector), its __next__() method is called once with a
|
||||
StopIteration instance argument. Together with the requirement
|
||||
that __next__() should always re-raise a StopIteration argument,
|
||||
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.
|
||||
|
||||
Note: the syntactic extensions to yield make it use very similar
|
||||
to that in Ruby. This is intentional. Do note that in Python the
|
||||
block passes a value to the generator using "continue EXPR" rather
|
||||
than "return EXPR", and the underlying mechanism whereby control
|
||||
is passed between the generator and the block is completely
|
||||
different. Blocks in Python are not compiled into thunks; rather,
|
||||
yield suspends execution of the generator's frame. Some edge
|
||||
cases work differently; in Python, you cannot save the block for
|
||||
later use, and you cannot test whether there is a block or not.
|
||||
|
||||
Alternatives Considered
|
||||
|
||||
TBD.
|
||||
|
||||
Examples
|
||||
|
||||
TBD.
|
||||
|
||||
Acknowledgements
|
||||
|
||||
In no useful order: Alex Martelli, Barry Warsaw, Bob Ippolito,
|
||||
Brett Cannon, Brian Sabbey, Doug Landauer, Fredrik Lundh, Greg
|
||||
Ewing, Holger Krekel, Jason Diamond, Jim Jewett, Josiah Carlson,
|
||||
Ka-Ping Yee, Michael Chermside, Michael Hudson, Nick Coghlan, Paul
|
||||
Moore, Phillip Eby, Raymond Hettinger, Samuele Pedroni, Shannon
|
||||
Behrens, Steven Bethard, Terry Reedy, Tim Delaney, Aahz, and
|
||||
others. Thanks all for a valuable discussion and ideas.
|
||||
|
||||
References
|
||||
|
||||
[1] http://mail.python.org/pipermail/python-dev/2005-April/052821.html
|
||||
|
||||
[2] http://msdn.microsoft.com/vcsharp/programming/language/ask/withstatement/
|
Loading…
Reference in New Issue