Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 3136 – Labeled break and continue

Author:
Matt Chisholm <matt- Python at theory.org>
Status:
Rejected
Type:
Standards Track
Created:
30-Jun-2007
Python-Version:
3.1
Post-History:


Table of Contents

Rejection Notice

This PEP is rejected. Seehttps://mail. Python.org/pipermail/ Python -3000/2007-July/008663.html.

Abstract

This PEP proposes support for labels in Python’sbreakand continuestatements. It is inspired by labeledbreakand continuein other languages, and the author’s own infrequent but persistent need for such a feature.

Introduction

Thebreakstatement allows the programmer to terminate a loop early, and thecontinuestatement allows the programmer to move to the next iteration of a loop early. In Python currently,break andcontinuecan apply only to the innermost enclosing loop.

Adding support for labels to thebreakandcontinuestatements is a logical extension to the existing behavior of thebreakand continuestatements. Labeledbreakandcontinuecan 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 thebreakstatement. However, all of the examples and motivations apply equally to labeledcontinue.

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 labeledbreakin Python (For this and future examples,...denotes an arbitrary number of intervening lines of code):

foraina_list:
time_to_break_out_of_a=False
...
forbinb_list:
...
ifcondition_one(a,b):
break
...
ifcondition_two(a,b):
time_to_break_out_of_a=True
break
...
iftime_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 fortime_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:

classBreakOutOfALoop(Exception):pass

foraina_list:
...
try:
forbinb_list:
...
ifcondition_one(a,b):
break
...
ifcondition_two(a,b):
raiseBreakOutOfALoop
...
exceptBreakOutOfALoop:
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 breakand 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:

foraina_list:
...
forbinb_list:
...
ifcondition_one(a,b):
break
...
ifcondition_two(a,b):
break
...
ifcondition_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 labeledbreakand 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 useslastinstead ofbreak.The BLOOP labels could be omitted;lastandcontinueapply 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 thebreakstatement 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 labeledbreakandcontinuewould 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 breakandcontinue(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 toolsbreakand continue,to make control flow easier to understand, not more difficult.

Labeledbreakandcontinuecannot 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,breakandcontinueby 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 optionalas orlabel(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).

Thebreak(andcontinue) 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 theas keyword:

foraina_listasa_loop:
...
forbinb_listasb_loop:
...
ifcondition_one(a,b):
breakb_loop# same as plain old break
...
ifcondition_two(a,b):
breaka_loop
...
...

Or, withlabelinstead ofas:

foraina_listlabela_loop:
...
forbinb_listlabelb_loop:
...
ifcondition_one(a,b):
breakb_loop# same as plain old break
...
ifcondition_two(a,b):
breaka_loop
...
...

This has all the benefits outlined above. It requires modifications to the language syntax: the syntax ofbreakandcontinue syntax statements and for and while statements. It requires either a new conditional keywordlabelor an extension to the conditional keywordas.[3]It is unlikely to require any changes to existing Python programs. Passing an identifier not defined in the local scope tobreakorcontinuewould raise a NameError.

Proposal B - Numeric break & continue

Rather than altering the syntax offorandwhileloops, breakandcontinuewould take a numeric argument denoting the enclosing loop which is being controlled, similar to PHP.

It seems more Pythonic to me forbreakandcontinueto refer to loops inde xing from zero, as opposed to inde xing from one as PHP does.

foraina_list:
...
forbinb_list:
...
ifcondition_one(a,b):
break0# same as plain old break
...
ifcondition_two(a,b):
break1
...
...

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

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

Proposal C - The reduplicative method

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

foraina_list:
...
forbinb_list:
...
ifcondition_one(a,b):
break# plain old break
...
ifcondition_two(a,b):
breakbreak
...
...

This would also allow the programmer to break out of the inner loop and continue the next outermost simply by writingbreakcontinue, [4]and so on. I’m not sure what exception would be raised if the programmer used morebreakorcontinue 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 tobreakout of orcontinuethat loop from within a deeper loop.

a_iter=iter(a_list)
foraina_iter:
...
b_iter=iter(b_list)
forbinb_iter:
...
ifcondition_one(a,b):
breakb_iter# same as plain old break
...
ifcondition_two(a,b):
breaka_iter
...
...

Passing a non-iterator object tobreakorcontinuewould 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 ofbreakand continuewas required. Instead of modifying the syntax of breakandcontinue,.break()and.continue()methods could be added to the Iterator type.

a_iter=iter(a_list)
foraina_iter:
...
b_iter=iter(b_list)
forbinb_iter:
...
ifcondition_one(a,b):
b_iter.break()# same as plain old break
...
ifcondition_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...whileloops
  • break LABEL vs. exceptions + PROPOSAL,on Python -list, as compared to using Exceptions for flow control
  • Named code blockson Python -list, a suggestion motivated by the desire for labeled break / continue
  • mod_ Python bug fixAn 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

Source:https://github / Python /peps/blob/main/peps/pep-3136.rst

Last modified:2023-09-09 17:39:29 GMT