[Python-checkins] python/nondist/peps pep-0340.txt,NONE,1.1

gvanrossum at users.sourceforge.net gvanrossum at users.sourceforge.net
Wed Apr 27 09:21:41 CEST 2005


Update of /cvsroot/python/python/nondist/peps
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv8461

Added Files:
	pep-0340.txt 
Log Message:
Initial draft.


--- NEW FILE: pep-0340.txt ---
PEP: 340
Title: Anonymous Block Statements
Version: $Revision: 1.1 $
Last-Modified: $Date: 2005/04/27 07:21:38 $
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/



More information about the Python-checkins mailing list