Version 14 from Greg.
This commit is contained in:
parent
8118396ca7
commit
46a9f7315e
273
pep-0380.txt
273
pep-0380.txt
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue