Python multi-level break and continue
A fairly lengthy discussion of whether there should be a way to break out of (or continue) more than one level of nested loops in Python recently took place in the Ideas category of the language's discussion forum. The idea is attractive, at least in an abstract sense—some other languages support jumping out of multiple loops at once—but it seems unlikely to go anywhere for Python. The barrier to new features is fairly high, for sure, but there is also a need for proponents to provide real-world examples that demonstrate their advantages. That, too, is a difficult bar to clear, as was seen in the discussion.
Idea
A user called "Python Millionaire" posted
an example of some loops that they had written to process data about some
basketball players; "I want to continue or break out of the nested loops
because I am no longer interested in the player
". They proposed
adding an integer to break and continue statements to
specify how
many loops to operate on. For example:
for player in all_players: for player_tables in all_tables: for version in player_tables: # things have gone wrong, need to break break 2 this_is_not_reached = True this_line_is_called()
If Python ever gets this feature, though, the (un-Pythonic?) integer
mechanism will surely not be part of it. It is terribly fragile when code
gets shuffled around, for one thing. Also, as Bryan Van de Ven observed,
it would be "a usability nightmare
" because it would be difficult "to
quickly locate the target of your fancy goto by means of a simple
code grep
".
This is not the first time the idea has come up; the feature was
raised 15 years ago by Matt Chisholm in PEP 3136 ("Labeled break
and continue"). The PEP was rejected
by Guido van Rossum for a variety of reasons, including a worry that the
feature would "be abused more than it will be
used right, leading to a net decrease in code clarity
". Peter Suter pointed
to the PEP in the discussion, noting that Millionaire "would
presumably need at least some very convincing examples that outweigh the
reasons given in the rejection notice
".
PEP 3136 offered several possibilities for the syntax of the feature, and did not choose one, which was another reason Van Rossum rejected it. But it seems clear that the labeled version is seen as the most viable path, even among those who are against adding the feature. A labeled break might look something like the following:
for a in a_list as a_loop: for b in b_list as b_loop: if ... break a_loopThat break would exit both loops immediately; a labeled continue would go to the next iteration of the named loop.
Millionaire thought
that after 15 years it might be time to reconsider the idea. They lamented
that the approaches suggested to work around the lack of multi-level
break are "infinitely clumsier
" and
"anti-pythonic
". Suter agreed with
that to a certain extent, noting the first search result for "python
multiple for loop break" is a Stack
Overflow answer that is overly clever. Suter adapted it
to the original example as follows:
for sport in all_sports: # "for sport" loop for player in all_players: for player_tables in all_tables: # "for player_tables" loop for version in player_tables: # things have gone wrong, go to next iteration of all_sports loop break else: continue break else: continue breakThat uses the else clause for loops, which will execute if no break is used in the loop, thus the loop runs to completion. So if the innermost loop runs to completion, the continue in the else will result in another iteration of the "for player_tables" loop. If the inner loop uses break, however, it will break twice more, all the way back to the "for sport" loop. As can be seen from that convoluted description, the construct is far from readable—or maintainable.
Other ways
There are multiple ways to accomplish what Millionaire is trying to do, some of which were described in the discussion. Using flags is one obvious, perhaps clunky, mechanism, another is to use exceptions, but that may not be much less clunky. Overall, though, several participants thought that the code itself should be refactored in some fashion. Chris Angelico thought that moving the search operation into its own function, which can return once the outcome is known, would simplify things. Steven D'Aprano agreed:
The obvious fix for that ugly code is to refactor into a function:def handle_inner_loops(sport): for player in all_players: for player_tables in all_tables: for version in player_tables: if condition: # things have gone wrong, bail out early. return block() for sport in all_sports: handle_inner_loops(sport)The solution to "Python needs a way to jump out of a chunk of code" is usually to put the chunk of code into a function, then return out of it.
Millionaire thought that requiring refactoring into a function was less than ideal. It is also not possible to implement a multi-level continue that way. Beyond that, Millionaire pushed back on the notion that labeled break/continue was a better syntactic choice in all cases; offering the numeric option too would give the most flexibility. There was little or no support for keeping the numeric version, however.
But the arguments given in support of the feature were generally fairly weak; they often used arbitrary, "made up" examples that demonstrated a place where multi-level break could be used, but were not particularly compelling. For example, "Gouvernathor" posted the following:
for system in systems: for planet in system: for moon in planet.moons: if moon.has_no_titanium: break 2 # I don't want to be in a system with a moon with no titanium if moon.has_atmosphere: break # I don't want to be in the same planetary system
As D'Aprano pointed
out, though, that is hardly realistic; "your example seems
so artificial, and implausible, as to be useless as a use-case for
multilevel break
". He reformulated the example in two different ways,
neither of which exactly duplicated the constraints of Gouvernathor's
example, however. He also had some thoughts on what it would take to
continue pursuing the feature:
To make this proposal convincing, we need a realistic example of an algorithm that uses it, and that example needs to be significantly more readable and maintainable than the refactorings into functions, or the use of try…except (also a localised goto).If you intend to continue to push this idea, I strongly suggest you look at prior art: find languages which have added this capability, and see why they added it.
Angelico noted that he has used the Pike programming language, which does have a labeled break. He found that he had used the feature twice in all of the Pike code he has written. Neither of the uses was particularly compelling in his opinion; one was in a quick-and-dirty script and the other is in need of refactoring if he were still working on that project, he said. That was essentially all of the real-world code that appeared in the discussion.
Paul Moore suggested
that needing a multi-level break may be evidence that the code
needs to be reworked; "Think of it in terms of 'having to break out of
multiple loops is a code smell, indicating that you should re-think your
approach'.
" Though he questioned the value of doing so, he did
offer
up a recent example:
I don't think everyone piling in with their code samples is particularly helpful. The most recent example I had, though, was a "try to fetch a URL 10 times before giving up" loop, inside the body of a function. I wanted to break out if one of the tries returned a 304 Not Modified status. A double-break would have worked. But in reality, stopping and thinking for a moment and factoring out the inner loop into a fetch_url function was far better, named the operation in a way that was more readable, and made the outer loop shorter and hence more readable itself.
Workarounds?
Millionaire complained
that all of the suggestions that had been made for ways to restructure the
code were workarounds of various sorts; "They are all ways of getting
around the problem, not actual solutions presented by the programming
language, which should be the case.
" But Oscar Benjamin said
that he could not "picture in my mind real maintainable code where
labelled break is significantly better than a reorganisation
".
All he can see in his mind is the feature "being used to extend the kind
of spaghetti code that I already wish people didn't write
". There is,
of course, an alternative: "if real life examples were provided then we
could discuss the pros and cons in those cases without depending on my
imagination
".
Meanwhile, others in the discussion pushed back against the workaround complaint and also reiterated calls for real-world code. Millionaire returned to his earlier basketball example, with a beefed-up version that uses labeled break and continue. While Millionaire seemed to think it was a perfectly readable chunk of code that way, others were less impressed. Angelico questioned some of the logic, while Van de Ven thought it did not demonstrate quite what Millionaire was claiming:
A 50-line loop body and eight levels of indentation (assuming this is inside a function) and this is the good version? Having a multi-breakwon'tdidn't fix that. All the complexity in that code stems from trying to do ad-hoc relational querying with imperative code, at the same time as pre- and post-processing.
Van de Ven and Millionaire went back and forth a few times, with Millionaire insisting that Van de Ven's refactorings and other suggestions were not mindful of various constraints (which were never mentioned up front, of course). Van de Ven thought that the episode was an example of an XY problem, where someone asks about their solution rather than their problem, but he still persisted in trying to show Millionaire alternative ways to structure their code. There are, seemingly, several avenues that Millionaire could pursue to improve their code overall, while also avoiding the need for multi-level break—if they wished to. But that is apparently not a viable path for Millionaire.
The discussion was locked by David Lord shortly thereafter; it was clear that it had run its course.
The convoluted examples presented in the thread were not particularly helpful to the cause, in truth. Users who want to add a feature to Python should have an eye on compelling use cases from the outset, rather than generalized feelings that "this would be a nice addition" to the language. If, for example, code from the standard library had been shown, where a multi-level break would have significantly improved it, the resurrected feature idea might have gained more traction. There are lots of other huge, open Python code bases out there, as well; any of those might provide reasonable examples. So far, at least, no one has brought anything like that to the fore.
This is something of a recurring theme in discussions about ideas for new Python features. To those who are proposing the feature, it seems like an extremely useful, rather straightforward addition to the language, but the reception to the idea is much different than expected. Python developers need to cast a critical eye on any change to the language and part of that is to determine whether the benefit outweighs the substantial costs of adopting it. That is not going to change, so it makes sense for those who are looking to add features to Python to marshal their arguments—examples—well.
Index entries for this article | |
---|---|
Python | Enhancements |
Posted Sep 1, 2022 0:03 UTC (Thu)
by k8to (guest, #15413)
[Link]
Posted Sep 1, 2022 0:22 UTC (Thu)
by dskoll (subscriber, #1630)
[Link] (17 responses)
I don't program in Python, but Perl does this with labels:
If Python has labels, this would seem a decent mechanism.
Posted Sep 1, 2022 1:16 UTC (Thu)
by k8to (guest, #15413)
[Link] (14 responses)
Python doesn't have labels or the attendant goto. continue exists to go to the next interation, but only the innermost loop.
Posted Sep 1, 2022 1:34 UTC (Thu)
by dtlin (subscriber, #36537)
[Link] (3 responses)
Posted Sep 1, 2022 7:53 UTC (Thu)
by tialaramex (subscriber, #21167)
[Link] (2 responses)
Rust's break can instead take a value (where appropriate) so that "break 2;" is actually valid Rust although it doesn't do what's proposed for Python (it means the inner loop terminates immediately with the value 2)
Posted Sep 1, 2022 22:26 UTC (Thu)
by dtlin (subscriber, #36537)
[Link] (1 responses)
break value is valid for loop. There's no other possible value for a loop expression, but a for/while loops can terminate normally, so it doesn't make sense to break value there.
To be clear though, what I was talking about is
where break 'label escapes both loops. This can be combined, e.g. break 'label value.
Posted Sep 4, 2022 22:59 UTC (Sun)
by tialaramex (subscriber, #21167)
[Link]
Posted Sep 1, 2022 14:42 UTC (Thu)
by flussence (guest, #85566)
[Link] (9 responses)
I think it'd be a good solution to Python's problem. I've used numbered breaks in PHP code and can't say it was much good for maintainability.
Posted Sep 1, 2022 16:31 UTC (Thu)
by rgmoore (✭ supporter ✭, #75)
[Link] (1 responses)
Perl's
Posted Sep 2, 2022 6:54 UTC (Fri)
by anselm (subscriber, #2796)
[Link]
Perl has goto mostly because it made the Awk-to-Perl translator easier to implement. It was never recommended for general usage by people in their own programs.
Posted Sep 1, 2022 20:39 UTC (Thu)
by bartoc (guest, #124262)
[Link] (6 responses)
It can jump "down" through a function though which sounds scary but in reality it's almost always used for jumping to cleanup code.
C goto is not the goto that Dijkstra was railing against, and is basically fine.
This whole discussion is honestly one of my biggest annoyances with the python community, it's pretty clear a lot of the code examples were being compared on just "personal preference", not any real concept of maintainability.
Posted Sep 1, 2022 23:12 UTC (Thu)
by tialaramex (subscriber, #21167)
[Link] (5 responses)
For the cleanup problem, I think C successors tend to offer defer here for that purpose. I don't like defer, but it's a more healthy solution than goto fail: type arrangements. I prefer something like Rust's Drop trait but I can see that feels too much like invisible magic for some C practitioners.
Posted Sep 1, 2022 23:35 UTC (Thu)
by NYKevin (subscriber, #129325)
[Link] (1 responses)
> For the cleanup problem, I think C successors tend to offer defer here for that purpose. I don't like defer, but it's a more healthy solution than goto fail: type arrangements. I prefer something like Rust's Drop trait but I can see that feels too much like invisible magic for some C practitioners.
People have come up with many, many different solutions to the cleanup problem:
* try-with-resources (Java)
Most of these options would require C to grow something vaguely resembling nontrivial data types (what C++ would call non-POD types), which will obviously never happen, but defer would make logical sense for C, IMHO. You could also make an argument for finally, because even without exceptions, it still intercepts early returns (as well as more convoluted nonsense like break and continue, and one imagines it could also do goto).
Posted Sep 1, 2022 23:36 UTC (Thu)
by NYKevin (subscriber, #129325)
[Link]
For people with no sense of humor: This is a joke. Do not actually do this.
Posted Sep 1, 2022 23:42 UTC (Thu)
by bartoc (guest, #124262)
[Link] (1 responses)
In any event I actually want all of goto, defer and drop/RAII. Defer is useful even with drop/RAII when you are interfacing with external code or doing something "one off". Goto remains useful for implementing certain algorithms involving state machines and tables.
While not super useful in combination with goto there is a related gcc extension called "labels as values" that I really wish would be standardized, since it allows writing code that would be almost impossible otherwise (like tracepoints).
Posted Sep 2, 2022 11:08 UTC (Fri)
by tialaramex (subscriber, #21167)
[Link]
There are (of course) several of these in Rust crates already, suiting different tastes but it's also trivial to build your own instead.
And for state transitions I'd definitely again rather have high level types representing this than try to do it with goto. Pattern matching and sum types make this sort of thing feel very ergonomic to me compared with C. In fact while I understand a C successor isn't going to have Rust's quite sophisticated ControlFlow type, it'd be nice to at least have say <control.h> defining CONTINUE and BREAK so that Library A's maybe_do_stuff() and Library B's repeat_until_done() both agree that CONTINUE is 1 (or 4, or 'Z' I don't care) while BREAK is 0 (or -26, or 5.16, again I don't care) rather than have a situation where Library A signals with -1 while Library B expects 0
Posted Sep 7, 2022 15:39 UTC (Wed)
by anton (subscriber, #25547)
[Link]
Posted Nov 29, 2023 16:16 UTC (Wed)
by blommaep (guest, #168263)
[Link] (1 responses)
Posted Dec 1, 2023 4:04 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link]
Posted Sep 1, 2022 2:28 UTC (Thu)
by gutschke (subscriber, #27910)
[Link]
Doesn't happen often, but every few years it's convenient. If memory serves correctly, in the vast majority of cases it was useful in order to jump out of all enclosing loops. In other words, I mostly used labeled breaks (or the equivalent goto statement) but can't remember the last time that I used labelled continues.
Importantly, in situations when I did use this feature, I didn't want to jump out of the entire function, because I still needed to do some final clean up or error handling. So, refactoring into a separate function wasn't always appropriate.
Of course, in Python, that's easily addressed by taking advantage of try/finally. The only remaining issue with this approach has to do with scoping and the lifetime of variables when refactoring into separate functions. But inner functions mostly address that concern. So, I agree that maybe in Python, this need for labelled breaks and continues just doesn't arise all that often.
Having said that, I can absolutely envision scenarios where a labeled break would be slightly more readable than a combination of inner functions and try/finally statements. Whether that's good enough to justify a new language feature is a different question.
Posted Sep 1, 2022 8:57 UTC (Thu)
by NRArnot (subscriber, #3033)
[Link] (2 responses)
That said, I like the labelled-loop suggestion.
Posted Sep 1, 2022 11:33 UTC (Thu)
by caliloo (subscriber, #50055)
[Link]
I find that one of the values of python is that it simply does not let you do too much dumb stuff language wise, no matter how in a hurry and clever you are… So for that reason I think I can live without it.
Posted Sep 1, 2022 12:32 UTC (Thu)
by Wol (subscriber, #4433)
[Link]
FOR I = 1 TO 10
It's a syntax error if the continue refers to a line-label that is not a valid loop terminator. Makes it dead easy to see where you are jumping to.
Cheers,
Posted Sep 1, 2022 9:28 UTC (Thu)
by pwfxq (subscriber, #84695)
[Link] (2 responses)
1 - Aren't exceptions supposed to be for "exceptional" circumstances/issues?
Posted Sep 1, 2022 11:49 UTC (Thu)
by petrm (subscriber, #126046)
[Link]
Posted Sep 1, 2022 16:30 UTC (Thu)
by k8to (guest, #15413)
[Link]
They would be awkward if you had a need to jump out to a variety of levels but i have difficulty imagining a usecase for that.
Posted Sep 1, 2022 10:44 UTC (Thu)
by alfille (subscriber, #1631)
[Link]
I know this has performance issues and perhaps variable scoping issues, but it also can add code clarity.
Posted Sep 1, 2022 12:01 UTC (Thu)
by jezuch (subscriber, #52988)
[Link] (3 responses)
In my 20+ years of software engineering experience I find this to be the optimal solution to the "problem". Splitting code into smaller functions is generally considered a Good Practice.
Labeled break seems like a useful(*) feature, but it's almost always an indication that the code is due for some refactoring. And it's a huge red flag sure to be drawing unwanted attention of predators (code reviewers) :)
(*) it also feels "neat". I suppose that's 80% of the reason our geek brains want it.
Posted Sep 1, 2022 12:42 UTC (Thu)
by eru (subscriber, #2753)
[Link] (1 responses)
As a contribution to the fun syntax bikeshedding, a language I was involved in implementing had a loop roughly like this:
Posted Sep 1, 2022 21:00 UTC (Thu)
by bartoc (guest, #124262)
[Link]
Keeping things inline can make optimization opportunities more clear and make anti-optimizations stick out more, as well.
Posted Sep 1, 2022 23:48 UTC (Thu)
by excors (subscriber, #95769)
[Link]
In those cases the code can be simpler and more clearly express the programmer's intent by using a multi-level break instead. They're not very common cases and the workarounds aren't too bad, so it's not a crucial language feature (as demonstrated by the successful languages without it), but it's also not a feature that seems to be widely abused or that causes great confusion (as demonstrated by the successful languages with it), so I think on balance it's still nicer if the language gives you the option.
Posted Sep 1, 2022 13:32 UTC (Thu)
by mathstuf (subscriber, #69389)
[Link]
Posted Sep 1, 2022 15:53 UTC (Thu)
by JoeBuck (subscriber, #2330)
[Link] (4 responses)
But I can't think of any language that uses a number of loops to jump out of as an argument to break/continue or next/exit.
So if it's done I think the label approach should be used.
Posted Sep 1, 2022 16:35 UTC (Thu)
by k8to (guest, #15413)
[Link] (1 responses)
Posted Sep 3, 2022 2:35 UTC (Sat)
by dskoll (subscriber, #1630)
[Link]
If by TCL you mean Ousterhout's Tcl, nope. break does not take an argument and nor does continue. Supplying one gives an error, at least in Tcl 8.6.11.
Posted Sep 4, 2022 23:09 UTC (Sun)
by intelfx (subscriber, #130118)
[Link] (1 responses)
Bash?
```
Posted Sep 7, 2022 18:28 UTC (Wed)
by jwilk (subscriber, #63328)
[Link]
Posted Sep 1, 2022 18:33 UTC (Thu)
by edeloget (subscriber, #88392)
[Link]
Frankly, in the name of readability, this looks like a very good candidate for modularisation. Your inner loop is exiled in a function that returns a specific value when it "break", and this function is called by the outer loop (which can then break if it receive said specific value when it calls it).
This has many other advantages, such as:
* not melting the mind of people who are reading your code ;
And if you are using a compiled language, there is a good chance that it will be able to optimize something (and if you're not using a compiled language then the additional time it would take to evaluate an additional function is not really an issue).
Posted Sep 1, 2022 20:44 UTC (Thu)
by bartoc (guest, #124262)
[Link] (2 responses)
Posted Sep 1, 2022 21:41 UTC (Thu)
by Wol (subscriber, #4433)
[Link]
of a *RELATIONAL* database.
Pick does not have a query planner - the optimal plan is "do as you're told".
Pick does not have an optimiser - okay, it's down to the user to make sure all your indices are properly declared, but in a well-designed database there is nothing to optimise.
Pick does have a compiler, but the object code lives in the database and is compiled at development time, not run time.
And the same is true for most other non-relational databases I would think. Hierarchical databases are mostly drill-down and optimised for fast access along common paths. Pick takes that to the next level - it's drill-down and optimised for fast access along any *sensible* path.
Cheers,
Posted Sep 2, 2022 7:23 UTC (Fri)
by NYKevin (subscriber, #129325)
[Link]
1. Python is slow.
Posted Sep 3, 2022 6:50 UTC (Sat)
by amarao (guest, #87073)
[Link] (1 responses)
Also, I want multi-level return, which forces outer functions immediate return. What a fut it would be!
Posted Sep 7, 2022 19:27 UTC (Wed)
by Wol (subscriber, #4433)
[Link]
DataBasic has, I believe, RETURN TO ... , which is probably what you're thinking of ...
I don't believe it is ever used in normal code! Certainly I've never seen it, let alone used it ...
Cheers,
Posted Sep 3, 2022 18:19 UTC (Sat)
by Shugyousha (subscriber, #93672)
[Link]
I don't think I have seen it in production code more than once or twice in ten years of writing Go code. It looked like a code smell then as well ...
Posted Sep 3, 2022 22:46 UTC (Sat)
by neilbrown (subscriber, #359)
[Link] (1 responses)
For a python-like language, I think a flag is the right approach, and if it appears clunky, then that is the fault of the language, and that is where we should look for a fix.
With a c-like for loop, it might appear
Maybe a "while" clause in the for loop?
Posted Sep 5, 2022 0:48 UTC (Mon)
by NYKevin (subscriber, #129325)
[Link]
> Maybe a "while" clause in the for loop?
I'm having a hard time seeing how that's supposed to be more readable than just writing break like we do now. Besides, if you really want while-like syntax, you can already write this:
I just don't see why we need to bolt on an extra clause to the for loop, given that you can just use break instead, or rewrite as a while loop. There are already two ways of doing it, why should we add a third?
Posted Sep 8, 2022 11:02 UTC (Thu)
by dboddie (guest, #55461)
[Link]
In any case, use of labels can help to make the intent clearer in the code, making it explicit which loop is being escaped or continued. In Python you would use a comment for that, perhaps, but you would need to be careful to update it if the code was refactored.
Posted Sep 8, 2022 15:31 UTC (Thu)
by wvaske (subscriber, #114127)
[Link] (4 responses)
Posted Sep 8, 2022 16:10 UTC (Thu)
by mathstuf (subscriber, #69389)
[Link] (3 responses)
Posted Sep 9, 2022 4:56 UTC (Fri)
by foom (subscriber, #14868)
[Link] (2 responses)
A context manager can already handle and optionally catch and suppress exceptions in its __exit__ handler. It cannot, currently, restart execution at the beginning of the 'with' region from __exit__, but that is an easy to imagine addition, which might even be useful for other things, e.g. "with retries(3): do_something_that_might_fail()"
Once you have that simple change, a for_loop context manager is basically implementable. The .break() and .continue() function calls simply throw a special exception type, which the exit handler swallows -- and then either restarts the with region or doesn't. Upon normal exit, the exit handler also determines whether to restart the region based on the loop condition.
Done.
Posted Sep 9, 2022 6:42 UTC (Fri)
by dtlin (subscriber, #36537)
[Link] (1 responses)
Posted Sep 19, 2022 12:04 UTC (Mon)
by sammythesnake (guest, #17693)
[Link]
It's easy to read, nicely encourages clear naming/labeling (at least in a much as a syntactic requirement to *have* a label), requires no extra charges to the language...
The only niggle is that the syntax of having a method call serve the purpose of a flow control construct is a *little" klunky...
It's even the kind of thing that might make a good library/module, even if an unusually small one :-P - perhaps a candidate to be included in an already existing standard library module (¿itertools?)
Some thoughts on how it could be expanded - more hands-holding support for more styles of loops, such as specific buttons for while/until/for/whatever. Perhaps parameters to the init of the Loop class would be the place to do that: "with Loop(while=<expr>): ..." or something.
Perhaps a future possibility would be syntactic support to adding this kind of context manager to other loops (like somebody above suggested using the "with" keyword) though that might end up with duplicate functionality if the context manager has already implemented features for specific loop like concepts, so that might be something to decide on sooner rather than later...
I'd also use a base class and child classes for the exception because a Boolean doesn't give much scope for distinguishing varieties of flow control conditions and there might be further future additions here (Possibly a retry the current loop option, for example...?)
Python multi-level break and continue
Python multi-level break and continue
OUTER:
for (my $i=0; $i<10; $i++) {
for (my $j=0; $j<10; $j++) {
if (ignore_this_j($j)) {
next; # Continues the inner loop
}
if (ignore_remaining_js($i, $j)) {
next OUTER; # Continues on the next $i loop
}
}
}
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
'outer: loop {
'inner: loop {
break 'outer;
}
}
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
next
and last
constructs only work with loop labels, but it does have a goto
statement that can jump to just about anywhere you tell it. Loop labels are one of those things people use when they feel they have to, but I don't think I've ever seen anyone use an actual goto
.
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
* with/using statements (Python/C#)
* defer (Go)
* Destructors (C++, Rust)
* finally (just about any language that supports exceptions, including several of the above)
* Higher-order functions like Haskell's withFile
* Optional types (which allow you to coalesce the happy path with the unhappy path so that you only have one code path and don't need to "handle" cleanup specially)
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
I don't find anything in "Go to Statement Considered Harmful" that suggests that Dijkstra meant only variants of goto that are more powerful than those in C.
Python multi-level break and continue
Python multi-level break and continue
It's easy to write: you never need to doubt where the break/continue will jump, no special constructions
It's easy to read: just find the label
I really fail to understand why it only exists in perl
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
It would be a useful tool for quick and dirty mods on top of existing things. But I think it would not get old in a code base really well. (I’m thinking interlaced label and breaks, now go read your code).
Python multi-level break and continue
...
CONTINUE I_WANT_OUT:
...
I_WANT_OUT: NEXT
Wol
Exceptions
2 - Unless you create a nest of exceptions, how do you control which level loop you're breaking back to?
Exceptions
Seems to me that the fact that an iterator has been fully drained is not exactly an unexpected event. That's bound to happen at some point. And yet, exception. (Because sure, how else are you going to signal this?) Using exceptions for multi-level loop exit seems rather similar to this use case.
Exceptions
Python multi-level break and continue
Python multi-level break and continue
Splitting code into smaller functions is often good practice, but not always. I often find it clearer to have the whole inline, so it can be seen at a glance, instead of lots of small functions used only once. The latter is like reading a text that keeps jumping into footnotes!
Python multi-level break and continue
while cond1
while cond2
...
// Want to break out of both loop levels?
if satisfied
break foundIt
endwhile
endwhile foundIt
The break is a jump forward, so it makes sense the label is at the endwhile. Of course this wont work too well in Python that lacks a delimiter to mark the end of the loop, other than decreasing indentation.
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
Multi-level break/continue with a loop label is part of Ada, VHDL, and Perl; all three spell 'continue' as 'next'; Ada and VHDL say 'exit' instead of 'break' and Perl says 'last'. Verilog has 'disable' which can be used to jump out of any labeled statement (which might be a loop or a begin/end block). But all of these rely on attaching a label to the region of code in question, so it's stable as the code is maintained (and perhaps extra loops are added).
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
Python multi-level break and continue
break [n]
Exit from within a for, while, until, or select loop. If n is specified, break n levels. n must be ≥ 1. If n is greater than the number of enclosing loops, all enclosing loops are exited. The return value is 0 unless n is not greater than or equal to 1.
```
Python multi-level break and continue
Python multi-level break and continue
* open yourself to reuse of the inner loop
* not needing to change the whole language to support a feature that will only make you sad and angry in the long run.
Python multi-level break and continue
Python multi-level break and continue
Wol
Python multi-level break and continue
2. Relational querying by hand implies relational querying in a language other than SQL, which is going to be harder to maintain since SQL is the standard language in this space.
3. Python comes with a bundled copy of SQLite, which has neither of the above problems and is ridiculously easy to set up and use.
Python multi-level break and continue
Python multi-level break and continue
Wol
Python multi-level break and continue
I would suggest that "break" and "continue" are the problem rather than the solution, and they should be deprecated.
That would, of course, require a somewhat richer looping/conditional construct.
I quite like the "Event indicators" described in https://pic.plover.com/knuth-GOTO.pdf (which is well worth reading in its entirity if you haven't already). They are only part of a solution, not a complete design, but I think they point in a good direction.
Python multi-level break and continue
for (piter = all_players.iter(); player = piter.next() ;)
for (ptiter, abort = all_tables.iter(), False; not abort and player_tables = ptiter.next();)
for (viter = player_tables.iter(); not abort and version = viter.next();)
# things have gone wrong, need to break
abort = True
this_is_not_reached = True
this_line_is_called()
This is clunky, but not because of the abort (I think) but because you are forced to split the 'init' and 'next' steps of walking the table.
Python combines these (good) but doesn't let anything be added (bad).
for player in all_player:
abort = False
for player_tables in all_tables while not abort:
for version in player_tables while not abort:
# things have gone wrong, need to break
abort = True
this_is_not_reached = True
this_line_is_called()
This makes it obvious from the start which loops might abort early. I think it is good to be obvious. With break, it might not be obvious until the end of the loop, which isn't good for readability (unless you keep all loops to 5 lines).
You still have a clunkiness that 'abort' must be initialised before the loop. Maybe this is good as it become trivial to know if the loop aborted.
(of course a good compiler would optimise the flag away and use gotos.)
Python multi-level break and continue
>
> [...]
piter = iter(all_player)
while player := next(piter) and not abort:
# etc.
Python multi-level break and continue
I really like the idea of context manager style for loops:
Python multi-level break and continue
for team in conference as conference_loop:
for player in team as team_loop:
if condition:
conference_loop.break()
else:
team_loop.continue()
It's pythonic and makes break
and continue
much more explicit. Even in normal code without a double break, knowing which iterator a break or continue belongs to would be helpful.
That would take some python internals updating but we could define our own for_loop context manager:
with for_loop(team, conference) as conference_loop:
with for_loop(player, team) as team_loop:
if condition:
conference_loop.break()
else:
team_loop.continue()
What happens with this?
Python multi-level break and continue
def control_flow_really_should_be_local(loop):
loop.break()
def i_mean_how_is_this_supposed_to_work(loop):
loop.continue()
with for_loop(team, conference) as conference_loop:
with for_loop(player, team) as team_loop:
if condition:
control_flow_really_should_be_local(conference_loop)
else:
i_mean_how_is_this_supposed_to_work(team_loop)
Python multi-level break and continue
Hypothetically, if __enter__ could be a generator, with the with body running for each yielded value, that would give us the power to implement
Python multi-level break and continue
class Loop:
def __enter__(self):
self._do_continue = True
while self._do_continue:
self._token = object()
yield LoopHandle(self._token)
def __exit__(self, type, value, traceback):
if type is LoopControlException and value._token is self._token:
self._do_continue = value._do_continue
return True
class LoopHandle:
def __init__(self, token):
self._token = token
def break_(self):
raise LoopControlException(False, self._token)
def continue_(self):
raise LoopControlException(True, self._token)
class LoopControlException(Exception):
def __init__(self, do_continue, token):
super().__init__()
self._do_continue = do_continue
self._token = token
with Loop() as outer:
with Loop() as inner:
outer.break_()
I'm undecided whether this is a good thing or not, but it was surprising to me how relatively little it would take to get there.
Python multi-level break and continue