Version 14 from Greg.

This commit is contained in:
Guido van Rossum 2010-10-28 23:17:00 +00:00
parent 8118396ca7
commit 46a9f7315e
1 changed files with 149 additions and 124 deletions

View File

@ -7,7 +7,7 @@ Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 13-Feb-2009
Python-Version: 2.7
Python-Version: 3.x
Post-History:
@ -40,8 +40,8 @@ much difficulty using a loop such as
::
for v in g:
yield v
for v in g:
yield v
However, if the subgenerator is to interact properly with the caller
in the case of calls to ``send()``, ``throw()`` and ``close()``, things
@ -63,7 +63,7 @@ generator:
::
yield from <expr>
yield from <expr>
where <expr> is an expression evaluating to an iterable, from which an
iterator is extracted. The iterator is run to exhaustion, during which
@ -71,52 +71,40 @@ time it yields and receives values directly to or from the caller of
the generator containing the ``yield from`` expression (the
"delegating generator").
Furthermore, when the iterator is another generator, the subgenerator is
allowed to execute a ``return`` statement with a value, and that value
becomes the value of the ``yield from`` expression.
Furthermore, when the iterator is another generator, the subgenerator
is allowed to execute a ``return`` statement with a value, and that
value becomes the value of the ``yield from`` expression.
In general, the semantics can be described in terms of the iterator
protocol as follows:
The full semantics of the ``yield from`` expression can be described
in terms of the generator protocol as follows:
* Any values that the iterator yields are passed directly to the
caller.
* Any values that the iterator yields are passed directly to the
caller.
* Any values sent to the delegating generator using ``send()``
are passed directly to the iterator. If the sent value is None,
the iterator's ``next()`` method is called. If the sent value is
not None, the iterator's ``send()`` method is called. Any exception
resulting from attempting to call ``next`` or ``send`` is raised
in the delegating generator.
* Any values sent to the delegating generator using ``send()``
are passed directly to the iterator. If the sent value is None,
the iterator's ``next()`` method is called. If the sent value is
not None, the iterator's ``send()`` method is called. If the call
raises StopIteration, the delegating generator is resumed. Any other
exception is propagated to the delegating generator.
* Exceptions passed to the ``throw()`` method of the delegating
generator are forwarded to the ``throw()`` method of the iterator.
If the iterator does not have a ``throw()`` method, its ``close()``
method is called if it has one, then the thrown-in exception is
raised in the delegating generator. Any exception resulting from
attempting to call these methods (apart from one case noted below)
is raised in the delegating generator.
* Exceptions other than GeneratorExit thrown into the delegating
generator are passed to the ``throw()`` method of the iterator.
If the call raises StopIteration, the delegating generator is resumed.
Any other exception is propagated to the delegating generator.
* The value of the ``yield from`` expression is the first argument
to the ``StopIteration`` exception raised by the iterator when it
terminates.
* If a GeneratorExit exception is thrown into the delegating generator,
or the ``close()`` method of the delegating generator is called, then
the ``close()`` method of the iterator is called if it has one. If this
call results in an exception, it is propagated to the delegating generator.
Otherwise, GeneratorExit is raised in the delegating generator.
* ``return expr`` in a generator causes ``StopIteration(expr)`` to
be raised.
* The value of the ``yield from`` expression is the first argument
to the ``StopIteration`` exception raised by the iterator when it
terminates.
Fine Details
------------
The implicit GeneratorExit resulting from closing the delegating
generator is treated as though it were passed in using ``throw()``.
An iterator having a ``throw()`` method is expected to recognize
this as a request to finalize itself.
If a call to the iterator's ``throw()`` method raises a StopIteration
exception, and it is *not* the same exception object that was thrown in,
and the original exception was not GeneratorExit, then the value of the
new exception is returned as the value of the ``yield from`` expression
and the delegating generator is resumed.
* ``return expr`` in a generator causes ``StopIteration(expr)`` to
be raised upon exit from the generator.
Enhancements to StopIteration
@ -130,67 +118,70 @@ are no arguments.
Formal Semantics
----------------
Python 3 syntax is used in this section.
1. The statement
::
RESULT = yield from EXPR
RESULT = yield from EXPR
is semantically equivalent to
::
_i = iter(EXPR)
try:
_y = _i.next()
except StopIteration, _e:
_r = _e.value
else:
while 1:
try:
_s = yield _y
except:
_m = getattr(_i, 'throw', None)
if _m is not None:
_x = sys.exc_info()
try:
_y = _m(*_x)
except StopIteration, _e:
if _e is _x[1] or isinstance(_x[1], GeneratorExit):
raise
else:
_r = _e.value
break
else:
_m = getattr(_i, 'close', None)
if _m is not None:
_m()
raise
else:
try:
if _s is None:
_y = _i.next()
else:
_y = _i.send(_s)
except StopIteration, _e:
_r = _e.value
break
RESULT = _r
_i = iter(EXPR)
try:
_y = next(_i)
except StopIteration as _e:
_r = _e.value
else:
while 1:
try:
_s = yield _y
except GeneratorExit as _e:
try:
_m = _i.close
except AttributeError:
pass
else:
_m()
raise _e
except BaseException as _e:
_x = sys.exc_info()
try:
_m = _i.throw
except AttributeError:
raise _e
else:
try:
_y = _m(*_x)
except StopIteration as _e:
_r = _e.value
break
else:
try:
if _s is None:
_y = next(_i)
else:
_y = _i.send(_s)
except StopIteration as _e:
_r = _e.value
break
RESULT = _r
except that implementations are free to cache bound methods for the 'next',
'send' and 'throw' methods of the iterator upon first use.
2. In a generator, the statement
::
return value
return value
is semantically equivalent to
::
raise StopIteration(value)
raise StopIteration(value)
except that, as currently, the exception cannot be caught by ``except``
clauses within the returning generator.
@ -199,14 +190,14 @@ clauses within the returning generator.
::
class StopIteration(Exception):
class StopIteration(Exception):
def __init__(self, *args):
if len(args) > 0:
self.value = args[0]
else:
self.value = None
Exception.__init__(self, *args)
def __init__(self, *args):
if len(args) > 0:
self.value = args[0]
else:
self.value = None
Exception.__init__(self, *args)
Rationale
@ -223,13 +214,26 @@ with references to variables in the surrounding scope, etc.), and
call the new function using a ``yield from`` expression.
The behaviour of the resulting compound generator should be, as far as
possible, exactly the same as the original unfactored generator in all
situations, including calls to ``next()``, ``send()``, ``throw()`` and
``close()``.
reasonably practicable, the same as the original unfactored generator
in all situations, including calls to ``next()``, ``send()``,
``throw()`` and ``close()``.
The semantics in cases of subiterators other than generators has been
chosen as a reasonable generalization of the generator case.
The proposed semantics have the following limitations with regard
to refactoring:
* A block of code that catches GeneratorExit without subsequently
re-raising it cannot be factored out while retaining exactly the
same behaviour.
* Factored code may not behave the same way as unfactored code if a
StopIteration exception is thrown into the delegating generator.
With use cases for these being rare to non-existent, it was not
considered worth the extra complexity required to support them.
Finalization
------------
@ -267,14 +271,14 @@ Using the proposed syntax, a statement such as
::
y = f(x)
y = f(x)
where f is an ordinary function, can be transformed into a delegation
call
::
y = yield from g(x)
y = yield from g(x)
where g is a generator. One can reason about the behaviour of the
resulting code by thinking of g as an ordinary function that can be
@ -338,16 +342,46 @@ attribute of the generator-iterator object, or returning it as the
value of the ``close()`` call to the subgenerator. However, the proposed
mechanism is attractive for a couple of reasons:
* Using the StopIteration exception makes it easy for other kinds
of iterators to participate in the protocol without having to
grow an extra attribute or a close() method.
* Using a generalization of the StopIteration exception makes it easy
for other kinds of iterators to participate in the protocol without
having to grow an extra attribute or a close() method.
* It simplifies the implementation, because the point at which the
return value from the subgenerator becomes available is the same
point at which StopIteration is raised. Delaying until any later
point at which the exception is raised. Delaying until any later
time would require storing the return value somewhere.
Rejected Ideas
--------------
Some ideas were discussed but rejected.
Suggestion: There should be some way to prevent the initial call to
next(), or substitute it with a send() call with a specified value,
the intention being to support the use of generators wrapped so that
the initial next() is performed automatically.
Resolution: Outside the scope of the proposal. Such generators should
not be used with ``yield from``.
Suggestion: If closing a subiterator raises StopIteration with a
value, return that value from the ``close()`` call to the delegating
generator.
Resolution: Undesirable for a number of reasons. The purpose of closing
a generator is to ensure proper cleanup, not to obtain a meaningful
return value. Also, it would be unreliable unless the return value were
stored so as to be available to subsequent close calls, which would
cause it to persist for longer than expected.
Suggestion: If ``close()`` is not to return a value, then raise an
exception if StopIteration with a non-None value occurs.
Resolution: No clear reason to do so. Ignoring a return value is not
considered an error anywhere else in Python.
Criticisms
==========
@ -355,7 +389,8 @@ Under this proposal, the value of a ``yield from`` expression would
be derived in a very different way from that of an ordinary ``yield``
expression. This suggests that some other syntax not containing the
word ``yield`` might be more appropriate, but no acceptable alternative
has so far been proposed.
has so far been proposed. Rejected alternatives include ``call``,
``delegate`` and ``gcall``.
It has been suggested that some mechanism other than ``return`` in
the subgenerator should be used to establish the value returned by
@ -364,21 +399,11 @@ the goal of being able to think of the subgenerator as a suspendable
function, since it would not be able to return values in the same way
as other functions.
The use of an argument to StopIteration to pass the return value
has been criticised as an "abuse of exceptions", without any
concrete justification of this claim. In any case, this is only
one suggested implementation; another mechanism could be used
without losing any essential features of the proposal.
It has been suggested that a different exception, such as
GeneratorReturn, should be used instead of StopIteration to return a
value. However, no convincing practical reason for this has been put
forward, and the addition of a ``value`` attribute to StopIteration
mitigates any difficulties in extracting a return value from a
StopIteration exception that may or may not have one. Also, using a
different exception would mean that, unlike ordinary functions,
'return' without a value in a generator would not be equivalent to
'return None'.
The use of an exception to pass the return value has been criticised
as an "abuse of exceptions", without any concrete justification of
this claim. In any case, this is only one suggested implementation;
another mechanism could be used without losing any essential features
of the proposal.
Alternative Proposals
@ -414,12 +439,12 @@ Copyright
This document has been placed in the public domain.
..
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
coding: utf-8
End:
Local Variables:
mode: indented-text
indent-tabs-mode: nil
sentence-end-double-space: t
fill-column: 70
coding: utf-8
End: