494 lines
15 KiB
ReStructuredText
494 lines
15 KiB
ReStructuredText
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. [#toowtdi]_
|
||
|
||
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 [#keyword]_ 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``. [#as]_ 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``,
|
||
[#breakcontinue]_ 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
|
||
=========
|
||
|
||
.. [#toowtdi] Breaking some loops with exceptions is inelegant because
|
||
it's a violation of There's Only One Way To Do It.
|
||
|
||
.. [#keyword] Or really any new contextual keyword that the community
|
||
likes: ``as``, ``label``, ``labeled``, ``loop``, ``name``, ``named``,
|
||
``walrus``, whatever.
|
||
|
||
.. [#as] 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.
|
||
|
||
.. [#breakcontinue] 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.
|
||
|
||
|
||
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:
|