Permalink
Cannot retrieve contributors at this time
493 lines (379 sloc)
15.1 KB
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: |