PEP: 3136 Title: Labeled break and continue Version: $Revision$
Last-Modified: $Date$ Author: Matt Chisholm <matt-python@theory.org>
Status: Rejected Type: Standards Track Content-Type: text/x-rst Created:
30-Jun-2007 Python-Version: 3.1 Post-History:

Rejection Notice

This PEP is rejected. See
https://mail.python.org/pipermail/python-3000/2007-July/008663.html.

Abstract

This PEP proposes support for labels in Python's break and continue
statements. It is inspired by labeled break and continue in other
languages, and the author's own infrequent but persistent need for such
a feature.

Introduction

The break statement allows the programmer to terminate a loop early, and
the continue statement allows the programmer to move to the next
iteration of a loop early. In Python currently, break and continue can
apply only to the innermost enclosing loop.

Adding support for labels to the break and continue statements is a
logical extension to the existing behavior of the break and continue
statements. Labeled break and continue can improve the readability and
flexibility of complex code which uses nested loops.

For brevity's sake, the examples and discussion in this PEP usually
refers to the break statement. However, all of the examples and
motivations apply equally to labeled continue.

Motivation

If the programmer wishes to move to the next iteration of an outer
enclosing loop, or terminate multiple loops at once, he or she has a few
less-than elegant options.

Here's one common way of imitating labeled break in Python (For this and
future examples, ... denotes an arbitrary number of intervening lines of
code):

    for a in a_list:
        time_to_break_out_of_a = False
        ...
        for b in b_list:
            ...
            if condition_one(a, b):
                break
            ...
            if condition_two(a, b):
                time_to_break_out_of_a = True
                break
            ...
        if time_to_break_out_of_a:
            break
        ...

This requires five lines and an extra variable, time_to_break_out_of_a,
to keep track of when to break out of the outer (a) loop. And those five
lines are spread across many lines of code, making the control flow
difficult to understand.

This technique is also error-prone. A programmer modifying this code
might inadvertently put new code after the end of the inner (b) loop but
before the test for time_to_break_out_of_a, instead of after the test.
This means that code which should have been skipped by breaking out of
the outer loop gets executed incorrectly.

This could also be written with an exception. The programmer would
declare a special exception, wrap the inner loop in a try, and catch the
exception and break when you see it:

    class BreakOutOfALoop(Exception): pass

    for a in a_list:
        ...
        try:
            for b in b_list:
                ...
                if condition_one(a, b):
                    break
                ...
                if condition_two(a, b):
                    raise BreakOutOfALoop
                ...
        except BreakOutOfALoop:
            break
        ...

Again, though; this requires five lines and a new, single-purpose
exception class (instead of a new variable), and spreads basic control
flow out over many lines. And it breaks out of the inner loop with break
and out of the other loop with an exception, which is inelegant.[1]

This next strategy might be the most elegant solution, assuming
condition_two() is inexpensive to compute:

    for a in a_list:
        ...
        for b in b_list:
            ...
            if condition_one(a, b):
                break
            ...
            if condition_two(a, b):
                break
            ...
        if condition_two(a, b)
            break
        ...

Breaking twice is still inelegant. This implementation also relies on
the fact that the inner (b) loop bleeds b into the outer for loop, which
(although explicitly supported) is both surprising to novices, and in my
opinion counter-intuitive and poor practice.

The programmer must also still remember to put in both breaks on
condition two and not insert code before the second break. A single
conceptual action, breaking out of both loops on condition_two(),
requires four lines of code at two indentation levels, possibly
separated by many intervening lines at the end of the inner (b) loop.

Other languages

Now, put aside whatever dislike you may have for other programming
languages, and consider the syntax of labeled break and continue. In
Perl:

    ALOOP: foreach $a (@a_array){
        ...
        BLOOP: foreach $b (@b_array){
            ...
            if (condition_one($a,$b)){
                last BLOOP; # same as plain old last;
            }
            ...
            if (condition_two($a,$b)){
                last ALOOP;
            }
            ...
        }
        ...
    }

(Notes: Perl uses last instead of break. The BLOOP labels could be
omitted; last and continue apply to the innermost loop by default.)

PHP uses a number denoting the number of loops to break out of, rather
than a label:

    foreach ($a_array as $a){
        ....
        foreach ($b_array as $b){
            ....
            if (condition_one($a, $b)){
                break 1;  # same as plain old break
            }
            ....
            if (condition_two($a, $b)){
                break 2;
            }
            ....
        }
        ...
    }

C/C++, Java, and Ruby all have similar constructions.

The control flow regarding when to break out of the outer (a) loop is
fully encapsulated in the break statement which gets executed when the
break condition is satisfied. The depth of the break statement does not
matter. Control flow is not spread out. No extra variables, exceptions,
or re-checking or storing of control conditions is required. There is no
danger that code will get inadvertently inserted after the end of the
inner (b) loop and before the break condition is re-checked inside the
outer (a) loop. These are the benefits that labeled break and continue
would bring to Python.

What this PEP is not

This PEP is not a proposal to add GOTO to Python. GOTO allows a
programmer to jump to an arbitrary block or line of code, and generally
makes control flow more difficult to follow. Although break and continue
(with or without support for labels) can be considered a type of GOTO,
it is much more restricted. Another Python construct, yield, could also
be considered a form of GOTO -- an even less restrictive one. The goal
of this PEP is to propose an extension to the existing control flow
tools break and continue, to make control flow easier to understand, not
more difficult.

Labeled break and continue cannot transfer control to another function
or method. They cannot even transfer control to an arbitrary line of
code in the current scope. Currently, they can only affect the behavior
of a loop, and are quite different and much more restricted than GOTO.
This extension allows them to affect any enclosing loop in the current
name-space, but it does not change their behavior to that of GOTO.

Specification

Under all of these proposals, break and continue by themselves will
continue to behave as they currently do, applying to the innermost loop
by default.

Proposal A - Explicit labels

The for and while loop syntax will be followed by an optional as or
label (contextual) keyword[2] and then an identifier, which may be used
to identify the loop out of which to break (or which should be
continued).

The break (and continue) statements will be followed by an optional
identifier that refers to the loop out of which to break (or which
should be continued). Here is an example using the as keyword:

    for a in a_list as a_loop:
        ...
        for b in b_list as b_loop:
            ...
            if condition_one(a, b):
                break b_loop  # same as plain old break
            ...
            if condition_two(a, b):
                break a_loop
            ...
        ...

Or, with label instead of as:

    for a in a_list label a_loop:
        ...
        for b in b_list label b_loop:
            ...
            if condition_one(a, b):
                break b_loop  # same as plain old break
            ...
            if condition_two(a, b):
                break a_loop
            ...
        ...

This has all the benefits outlined above. It requires modifications to
the language syntax: the syntax of break and continue syntax statements
and for and while statements. It requires either a new conditional
keyword label or an extension to the conditional keyword as.[3] It is
unlikely to require any changes to existing Python programs. Passing an
identifier not defined in the local scope to break or continue would
raise a NameError.

Proposal B - Numeric break & continue

Rather than altering the syntax of for and while loops, break and
continue would take a numeric argument denoting the enclosing loop which
is being controlled, similar to PHP.

It seems more Pythonic to me for break and continue to refer to loops
indexing from zero, as opposed to indexing from one as PHP does.

    for a in a_list:
        ...
        for b in b_list:
            ...
            if condition_one(a,b):
                break 0  # same as plain old break
            ...
            if condition_two(a,b):
                break 1
            ...
        ...

Passing a number that was too large, or less than zero, or non-integer
to break or continue would (probably) raise an IndexError.

This proposal would not require any changes to existing Python programs.

Proposal C - The reduplicative method

The syntax of break and continue would be altered to allow multiple
break and continue statements on the same line. Thus, break break would
break out of the first and second enclosing loops.

    for a in a_list:
        ...
        for b in b_list:
            ...
            if condition_one(a,b):
                break  # plain old break
            ...
            if condition_two(a,b):
                break break
            ...
        ...

This would also allow the programmer to break out of the inner loop and
continue the next outermost simply by writing break continue, [4] and so
on. I'm not sure what exception would be raised if the programmer used
more break or continue statements than existing loops (perhaps a
SyntaxError?).

I expect this proposal to get rejected because it will be judged too
difficult to understand.

This proposal would not require any changes to existing Python programs.

Proposal D - Explicit iterators

Rather than embellishing for and while loop syntax with labels, the
programmer wishing to use labeled breaks would be required to create the
iterator explicitly and assign it to an identifier if he or she wanted
to break out of or continue that loop from within a deeper loop.

    a_iter = iter(a_list)
    for a in a_iter:
        ...
        b_iter = iter(b_list)
        for b in b_iter:
            ...
            if condition_one(a,b):
                break b_iter  # same as plain old break
            ...
            if condition_two(a,b):
                break a_iter
            ...
        ...

Passing a non-iterator object to break or continue would raise a
TypeError; and a nonexistent identifier would raise a NameError. This
proposal requires only one extra line to create a labeled loop, and no
extra lines to break out of a containing loop, and no changes to
existing Python programs.

Proposal E - Explicit iterators and iterator methods

This is a variant of Proposal D. Iterators would need be created
explicitly if anything other that the most basic use of break and
continue was required. Instead of modifying the syntax of break and
continue, .break() and .continue() methods could be added to the
Iterator type.

    a_iter = iter(a_list)
    for a in a_iter:
        ...
        b_iter = iter(b_list)
        for b in b_iter:
            ...
            if condition_one(a,b):
                b_iter.break()  # same as plain old break
            ...
            if condition_two(a,b):
                a_iter.break()
            ...
        ...

I expect that this proposal will get rejected on the grounds of sheer
ugliness. However, it requires no changes to the language syntax
whatsoever, nor does it require any changes to existing Python programs.

Implementation

I have never looked at the Python language implementation itself, so I
have no idea how difficult this would be to implement. If this PEP is
accepted, but no one is available to write the feature, I will try to
implement it myself.

Footnotes

Resources

This issue has come up before, although it has never been resolved, to
my knowledge.

-   labeled breaks__, on comp.lang.python, in the context of do...while
    loops

    __
    http://groups.google.com/group/comp.lang.python/browse_thread/thread/6da848f762c9cf58/979ca3cd42633b52?lnk=gst&q=labeled+break&rnum=3#979ca3cd42633b52

-   break LABEL vs. exceptions + PROPOSAL__, on python-list, as compared
    to using Exceptions for flow control

    __
    https://mail.python.org/pipermail/python-list/1999-September/#11080

-   Named code blocks__ on python-list, a suggestion motivated by the
    desire for labeled break / continue

    __ https://mail.python.org/pipermail/python-list/2001-April/#78439

-   mod_python bug fix__ An example of someone setting a flag inside an
    inner loop that triggers a continue in the containing loop, to work
    around the absence of labeled break and continue

    __
    http://mail-archives.apache.org/mod_mbox/httpd-python-cvs/200511.mbox/%3C20051112204322.4010.qmail@minotaur.apache.org%3E

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:

[1] Breaking some loops with exceptions is inelegant because it's a
violation of There's Only One Way To Do It.

[2] Or really any new contextual keyword that the community likes: as,
label, labeled, loop, name, named, walrus, whatever.

[3] The use of as in a similar context has been proposed here,
http://sourceforge.net/tracker/index.php?func=detail&aid=1714448&group_id=5470&atid=355470
but to my knowledge this idea has not been written up as a PEP.

[4] To continue the Nth outer loop, you would write break N-1 times and
then continue. Only one continue would be allowed, and only at the end
of a sequence of breaks. continue break or continue continue makes no
sense.