Source

python-peps / pep-3136.txt

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
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 http://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 a 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

  __ http://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

  __ http://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:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.