[Python-checkins] python/nondist/peps pep-0340.txt,1.12,1.13
gvanrossum@users.sourceforge.net
gvanrossum at users.sourceforge.net
Mon May 2 05:30:09 CEST 2005
Update of /cvsroot/python/python/nondist/peps
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv28163
Modified Files:
pep-0340.txt
Log Message:
Establish the "alternative" version. The exception API is called
__exit__(), and its signature is the same as that of the
raise-statement. Still some loose ends.
Index: pep-0340.txt
===================================================================
RCS file: /cvsroot/python/python/nondist/peps/pep-0340.txt,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -d -r1.12 -r1.13
--- pep-0340.txt 29 Apr 2005 18:51:03 -0000 1.12
+++ pep-0340.txt 2 May 2005 03:30:07 -0000 1.13
@@ -26,30 +26,6 @@
this on python-dev recently [1], and I figured it would be time to
write up a precise spec in PEP form.
-Proposal Evolution
-
- The discussion on python-dev has changed my mind slightly on how
- exceptions should be handled, but I don't have the time to do a
- full update of the PEP right now. Basically, I'm now in favor of
- a variation on the exception handling proposed in the section
- "Alternative __next__() and Generator Exception Handling" below.
-
- The added twist is that instead of adding a flag argument to
- next() and __next__() to indicate whether the previous argument is
- a value or an exception, we use a separate API (an __exit__()
- method taking an exception and perhaps a traceback) for the
- exception. If an iterator doesn't implement __exit__(), the
- exception is just re-raised. It is expected that, apart from
- generators, very few iterators will implement __exit__(); one use
- case would be a fast implementation of synchronized() written in
- C.
-
- The built-in next() function only interfaces to the next() and
- __next__() methods; there is no user-friendly API to call
- __exit__(). (Or perhaps calling next(itr, exc, traceback) would
- call itr.__exit__(exc, traceback) if itr has an __exit__ method
- and otherwise raise exc.__class__, exc, traceback?)
-
Motivation and Summary
(Thanks to Shane Hathaway -- Hi Shane!)
@@ -96,40 +72,25 @@
TBD. For now, see the Examples section near the end.
-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
+ takes one optional argument, which defaults to None. 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.
+ The argument to the __next__() method may be used by the iterator
+ as a hint on what to do next.
+
+Specification: the __exit__() Method
+
+ An optional new method for iterators is proposed, called
+ __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
+ consulted to provide suitable default values.
Specification: the next() Built-in Function
@@ -143,34 +104,43 @@
return itr.next()
raise TypeError("next() with arg for old-style iterator")
-Specification: the 'for' Loop
+ This function is proposed because there is often a need to call
+ the next() method outside a for-loop; the new API, and the
+ backwards compatibility code, is too ugly to have to repeat in
+ user code.
+
+ Note that I'm not proposing a built-in function to call the
+ __exit__() method of an iterator. I don't expect that this will
+ be called much outside the block-statement.
+
+Specification: a Change to 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
+ brk = False
while True:
try:
VAR1 = next(itr, arg)
except StopIteration:
+ brk = True
break
arg = None
BLOCK1
+ if brk:
+ BLOCK2
- (However, 'itr' 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.)
-
- I'm leaving the translation of an else-clause up to the reader;
- note that you can't simply affix the else-clause to the while-loop
- since it is always broken out.
+ (However, the variables 'itr' etc. are not user-visible and the
+ built-in names used cannot be overridden by the user.)
Specification: the Extended 'continue' Statement
@@ -180,7 +150,7 @@
is legal and is translated into
- arg = ContinueIteration(EXPR2)
+ arg = EXPR2
continue
(Where 'arg' references the corresponding hidden variable from the
@@ -195,21 +165,28 @@
block EXPR1 as VAR1:
BLOCK1
+ else:
+ BLOCK2
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 "as VAR1" part is optional; if omitted, the assignments to
+ VAR1 in the translation below are omitted (but the expressions
+ assigned are still evaluated!).
- 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].)
+ The choice of the 'block' keyword is contentious; many
+ alternatives have been proposed, including not to use a keyword at
+ 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
+ with their reasoning.) To sidestep this issue momentarily I'm
+ using 'block' until we can agree on the right keyword, if any.
+
+ Note that the 'as' keyword is not contentious (it will finally be
+ elevated to proper keyword status).
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
@@ -218,61 +195,66 @@
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:
+ be notified when the block-statement is left, regardless if this
+ is due to a break, return or exception:
- itr = EXPR1
- val = arg = None
- ret = False
+ itr = EXPR1 # The iterator
+ ret = False # True if a return statement is active
+ val = None # Return value, if ret == True
+ arg = None # Argument to __next__() (value from continue)
+ exc = None # sys.exc_info() tuple if an exception is active
while True:
try:
- VAR1 = next(itr, arg)
+ if exc:
+ ext = getattr(itr, "__exit__", None)
+ if ext is not None:
+ VAR1 = ext(*exc) # May re-raise *exc
+ else:
+ raise *exc # Well, the moral equivalent :-)
+ else:
+ VAR1 = next(itr, arg) # May raise StopIteration
except StopIteration:
if ret:
return val
- if val is not None:
- raise val
break
try:
- val = arg = None
ret = False
+ val = arg = exc = None
BLOCK1
- except Exception, val:
- arg = StopIteration()
-
- (Again, 'itr' etc. are hidden, and the user cannot override the
- built-ins.)
+ except:
+ exc = sys.exc_info()
- The "raise val" 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.
+ (Again, the variables and built-ins are hidden from the user.)
Inside BLOCK1, the following special translations apply:
- "continue" and "continue EXPR2" are always legal; the latter is
translated as shown earlier:
- arg = ContinueIteration(EXPR2)
+ arg = EXPR2
continue
- "break" is always legal; it is translated into:
- arg = StopIteration()
+ exc = (StopIteration,)
continue
- "return EXPR3" is only legal when the block-statement is
contained in a function definition; it is translated into:
- val = EXPR3
+ exc = (StopIteration,)
ret = True
- arg = StopIteration()
+ val = EXPR3
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.
+ block-statement is 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, there is no difference with a for-loop (except
+ that a for-loop calls iter() on EXPR1).
Note that a yield-statement (or a yield-expression, see below) in
a block-statement is not treated differently. It suspends the
@@ -287,15 +269,18 @@
be resumed eventually.
I haven't decided yet whether the block-statement should also
- allow an optional else-clause, like the for-loop. 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.
+ allow an optional else-clause, like the for-loop, but I'm leaning
+ against it. 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
+ possible semantics for an else-clause.
Specification: Generator Exception Handling
Generators will implement the new __next__() method API, as well
- as the old argument-less next() method.
+ as the old argument-less next() method which becomes an alias for
+ calling __next__() without an argument. They will also implement
+ the new __exit__() method API.
Generators will be allowed to have a yield statement inside a
try-finally statement.
@@ -330,32 +315,41 @@
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 __exit__() is called, the generator is resumed but at the
+ point of the yield-statement or -expression the exception
+ represented by the __exit__ argument(s) is raised. The generator
+ may re-raise this exception, raise another exception, or yield
+ another value, execpt 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 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 __next__() is called with an argument that is not None, the
+ yield-expression that it resumes will return the value attribute
+ of the argument. If it resumes a yield-statement, the value is
+ ignored (or should this be considered an error?). When the
+ *initial* call to __next__() receives an argument that is not
+ None, the generator's execution is started normally; the
+ argument's value attribute is ignored (or should this be
+ considered an error?). When __next__() is called without an
+ argument or with None as argument, and a yield-expression is
+ resumed, the yield-expression returns None.
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.
+ 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
+ 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 its use very similar
to that in Ruby. This is intentional. Do note that in Python the
@@ -367,71 +361,6 @@
cases work differently; in Python, you cannot save the block for
later use, and you cannot test whether there is a block or not.
-Specification: Alternative __next__() and Generator Exception Handling
-
- The above specification doesn't let the generator handle general
- exceptions. If we want that, we could modify the __next__() API
- to take either a value or an exception argument, with an
- additional flag argument to distinguish between the two. When the
- second argument is True, the first must be an Exception instance,
- which raised at the point of the resuming yield; otherwise the
- first argument is the value that is returned from the
- yield-expression (or ignored by a yield-statement). Wrapping a
- regular value in a ContinueIteration is then no longer necessary.
-
- The next() built-in would be modified likewise:
-
- def next(itr, arg=None, exc=False):
- nxt = getattr(itr, "__next__", None)
- if nxt is not None:
- return nxt(arg, exc)
- if arg is None and not exc:
- return itr.next()
- raise TypeError("next() with args for old-style iterator")
-
- The translation of a block-statement would become:
-
- itr = EXPR1
- arg = val = None
- ret = exc = False
- while True:
- try:
- VAR1 = next(itr, arg, exc)
- except StopIteration:
- if ret:
- return val
- break
- try:
- arg = val = None
- ret = exc = False
- BLOCK1
- except Exception, arg:
- exc = True
-
- The translation of "continue EXPR2" would become:
-
- arg = EXPR2
- continue
-
- The translation of "break" inside a block-statement would become:
-
- arg = StopIteration()
- exc = True
- continue
-
- The translation of "return EXPR3" inside a block-statement would
- become:
-
- val = EXPR3
- arg = StopIteration()
- ret = exc = True
- continue
-
- The translation of a for-loop would be the same as indicated
- earlier (inside a for-loop only the translation of "continue
- EXPR2" is changed; break and return translate to themselves in
- that case).
-
Loose Ends
These are things that need to be resolved before accepting the
@@ -447,17 +376,8 @@
- Decide on the keyword ('block', 'with', '@', nothing, or
something else?).
- - Phillip Eby wants a way to pass tracebacks along with
- exceptions.
-
- - The translation for the for-loop's else-clause.
-
- Whether a block-statement should allow an else-clause.
- - Which API to use to pass in an exception: itr.__next__(exc),
- itr.__next__(exc, True) or itr.__exit__(exc[, traceback]).
- Hmm..., perhaps itr.__next__(exc, traceback)?
-
Comparison to Thunks
Alternative semantics proposed for the block-statement turn the
@@ -626,7 +546,7 @@
block auto_retry(3, IOError):
f = urllib.urlopen("http://python.org/peps/pep-0340.html")
- print f.read()
+ print f.read()
5. It is possible to nest blocks and combine templates:
More information about the Python-checkins
mailing list