15 ms·
Python multi-level break and continue
- rich_sasha 4y agoWalrus operator? In. F string interpolation? In. Pattern matching? Hacked in. Genuinely useful feature that is awkward to emulate? Nah. Let's face it, everyone writes a double or triple nested for loop every now and then.
- BiteCode_dev 4y agoPython has had itertools.product for ages for this use case.
- masklinn 4y agoproduct only covers independent nested iterations, some of them have dependencies. But for those you've got iterools.concat, or generator comprehensions.
- joshuamorton 4y agoIt is weird to add a feature to the language that would be ~immediately banned by every style guide worth its salt. Python is already expressive enough that unlabeled break and continue are usually the wrong choice.
- rich_sasha 4y ago> unlabeled break and continue are usually the wrong choice. Citation needed. You can of course write code so they are not needed, but it won't necessarily be clearer, more efficient or "better" in any way. That sometimes they facilitate spaghetti code is no reason no ban them.
- joshuamorton 4y ago> Citation needed. You can of course write code so they are not needed, but it won't necessarily be clearer, more efficient or "better" in any way. If efficiency truly matters, you should not be using break or continue, you should be using a vectorized processing library like numpy or pandas. The pattern I describe in https://news.ycombinator.com/item?id=32710822 https://news.ycombinator.com/item?id=32710822 works for continue, and the same general concept works for break (and note that I've made this as unappealing as possible for my refactor, and it's still not really worse, but could easily be made even better): for i in x: if cond(i): break return do_a_thing(x) can become def find(x): for i in x: if cond(i): return i do_a_thing(find(x)) and these two patterns generalize to most more complex examples, and are fundamentally easier to follow, as the code is doing fewer things at any given time! Like break and return from a loop are equivalent modulo implicit state, and the same is true for continue and a conditional generator.
- rich_sasha 4y agoThey are not necessarily easier to follow, since you chop your logic into different functions. This may be better, but isn't necessarily. Esp if the loop has some (gasp) state which you then need to pass around to the outsourced functions. Not all loops ate numeric, in fact most aren't (since vectorisable stuff already is vectorised).
- joshuamorton 4y ago> Esp if the loop has some (gasp) state which you then need to pass around to the outsourced functions. Being explicit about which state needs to be passed is almost universally better than having implicit state in a large complex function. If you're dealing with a complicated function tracking a large amount of state, separating the state needed to iterate over the loop, and the state needed to validate whatever condition you're checking is less complex than keeping it all in one larger function. The number of potential interactions grow exponentially in the number of local variables. Less state (fewer locals) means exponentially less complexity. When dealing with very small numbers of locals, the additional confusion from a second function might win out, but any situation where you're forced to pass state between the loop and the conditional is going to be over the threshold where multiple functions are easier for most people to follow. And that's just stateful interactions, the functions I've written have precisely one entrance and exit point, and precisely one path through them, while break and continue create opportunities for more complicated control flow. You're arguing that a function with more local state and more complex control structures should be simpler in some cases, but I don't see that happening as the state and control structures get even more complex. This is of course made obvious by the fact that the first code example I posted contains a bug, while the second avoids it.
- ledauphin 4y ago"Citation needed" is an unproductive demand when we're discussing our opinions about programming language style and design. Or do you think your own assertions about labeled break should be met with the same demand?
- rich_sasha 4y ago"I would find this useful" is self-supporting. "Break and continue are almost always the wrong choice" is not, and indeed dissing one the oldest and most retained features in programming language history requires substantial justification.
- stinos 4y agoevery now and then Exactly; at least for me the other features you mention I use daily (or when considering coding hours only, multiple times per hour), whereas I cannot even exactly remember when the last time was I had to break out of a nested loop. I'm not saying it's not useful, just that I don't think that code frequency is a useful argument to make here, especially not in comparison with the features you mention.
- rich_sasha 4y agoSee I'm the other way round. I don't use walrus op, I dislike f strings because the "operator" f is to the left, whereas I'm used to "filling" a strong from the right. Pattern matching scares me, since unless you're working with data classes, the contents of a class can be only loosely related to its constructor. Loops i write daily. This feels like a bunch of people projecting their subjective biases on what code should look like on everyone else. Python 2/3 all over again (in a small teacup).
- deleted 4y ago[deleted]
- norhi999 4y agoI don't get why they discarded for/while... as label1: __for/while... as label2: ___break label1 suggestion. It actually seems a good enough idea to implement that. And it's rather concise. I often need to break deep inner loops to outermost and doing it with flags is... strange.
- hansvm 4y agoMildly on-topic, Zig has basically that behavior, and it's really smooth in my experience.
- eesmith 4y agoCould you give a real-world example of how it proved useful? Eg, quoting the text: > 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.
- IshKebab 4y agoRust supports this. It appears to be used more than twice... https://grep.app/search?q=break%20%27%5Cw®exp=true&case=true&filter[lang][0]=Rust https://grep.app/search?q=break%20%27%5Cw®exp=true&case=t...
- eesmith 4y agoMy example was "twice by one developer", not "twice across all indexed repos." A spot check shows that quite a few in your link are used specifically to ensure correct handling of Rust multi-level breaks work syntax, like https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide/src/highlight_related.rs#L746 https://github.com/rust-lang/rust-analyzer/blob/master/crate... , https://github.com/rust-lang/rustfmt/blob/master/tests/source/label_break.rs https://github.com/rust-lang/rustfmt/blob/master/tests/sourc... , https://github.com/rust-lang/rust/blob/master/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_label_to_loop.rs https://github.com/rust-lang/rust/blob/master/src/tools/rust... , https://github.com/rust-lang/rust/blob/master/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs https://github.com/rust-lang/rust/blob/master/src/tools/rust... and likely more. Another is a translation of BASIC code to Rust, using break as a form of goto. https://github.com/coding-horror/basic-computer-games/blob/e93a709930bfed713a06fc8c240447c05e0a2645/65_Nim/rust/src/main.rs#L170 https://github.com/coding-horror/basic-computer-games/blob/e... . The Python version at https://github.com/coding-horror/basic-computer-games/blob/e93a709930bfed713a06fc8c240447c05e0a2645/65_Nim/python/Traditional_NIM.py https://github.com/coding-horror/basic-computer-games/blob/e... doesn't use that approach. The example at https://github.com/tokio-rs/mio/blob/master/tests/tcp.rs https://github.com/tokio-rs/mio/blob/master/tests/tcp.rs is a nice one // Wait for our TCP stream to connect 'outer: loop { poll.poll(&mut events, None).unwrap(); for event in events.iter() { if event.token() == Token(1) && event.is_writable() { break 'outer; } } } though it can be replaced with a helper-function (note: I don't fully know what the code is doing, but the following looks right): def find_writable_event(events): for event in events: if event.token() == Token(1) and event.is_writable(): return True while 1: poll.poll(events, None) if find_writable_event(events): break So while there's likely a good real-world example of something which can't easily be re-written in an alternative form for Python, simply pointing to a grep result isn't all that persuasive.
- artificialidiot 4y agoI think it boils down to "early exit" v.s. "single return" style. More than likely, the average programmer will pick the style they've been taught at school or whichever was preached more. I think early exit constructs improve readability and python already offers a lot of syntax open to abuse at this point. (things you can write with nested list/dict comprehensions with if conditions/expressions scattered..)
- greatgib 4y agoPersonally I like very much the solution proposed at the end of comments: https://lwn.net/Articles/907510/ https://lwn.net/Articles/907510/ With something like: For loop... As my_loop: my_loop.continue()
- mangecoeur 4y agoYou might as well implement goto while you’re at it.
- analog31 4y agoAt least GOTO is readable to a layperson in terms of what happens after the break. And Python has sprawled to the point where it's important to make things readable by people who have not memorized the entire language.
- dataflow 4y ago> 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. > 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). How about this: found = None for r, row in enumerate(table.rows): for c, cell in enumerate(row): if search_query.matches(cell.value): found = (r, c) break 2 logger.log("Found cell: {}".format(found)) return found
- gurchik 4y agoThat doesn't look to be "significantly more readable" than: def find_cell(table): for r, row in enumerate(table.rows): for c, cell in enumerate(row): if search_query.matches(cell.value): return (r, c) found = find_cell(table) logger.log("Found cell: {}".format(found))
- dataflow 4y agoIt sure looks more readable/maintainable to me than if it was refactored into a function. Could you try refactoring it and showing what it would look like in your mind that would be similar in that respect?
- awinter-py 4y agohave always wanted `break break` syntax for this kind of thing for x in a1: for y in a2: if test(y): break break # break both outer loops in general, I wonder if there's a class of 'tree-like' control structures which can break multiple levels under certain circumstances an application might be scope management parsing -- I tried to build something like this a while ago, not clear that it's better than parser-generators but it's certainly different
- tempxyz 4y agoSwift let's you declare a label, eg: "outer", in front of the outer for loop, then you can declare "break outer".
- BiteCode_dev 4y agoItertools is your friend: from itertools import product for x, y in product(a1, a2): if test(y): break Not only you get the behavior you want, but you get an easier to read code with one less level of indentation. Bonus, you can now use "else" (which most people don't know can be used with "for" and not just "if"). Why introduce a new syntax when a library does the job? A new syntax is a huge bar to pass for a language, and nested loops are rare. Itertools is a gem, and any python dev will benefit greatly from mastering it.
- rich_sasha 4y agoIt is markedly less efficient since you will test y every time for each x, as opposed to once. O(1) vs O(n)
- nemoniac 4y agoCommon Lisp has a system of Conditions and Restarts which can do this and more. The link below describes an example of handling errors which arise when reading a collection of files line by line. https://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html https://gigamonkeys.com/book/beyond-exception-handling-condi...
- phoe-krk 4y agoNo, you don't even need the condition system to break out of nested iteration. (loop named loop-a do (loop named loop-b do (loop named loop-c do (return-from loop-b))) (return-from loop-a 42)) Under the hood, this uses CL:BLOCK; you can also use it yourself to mark where you want to be able to return from within the lexical scope of your code.
- chkas 4y agoI argue for a "break" with a number that indicates how many loops you want to break out of. I have this in my programming language https://easylang.online https://easylang.online. Each "break" needs the number, so also a "break 1". for i = 1 to 50 for j = 1 to 50 if i * i + j * j * j = 80036 break 2 end end end print i & " " & j
- ajanuary 4y ago1) I need to count the loops to work out where it breaks. 2) It’s sensitive to refactoring changing the nesting. That seems strictly worse than labelled breaks. What are the upsides?
- chkas 4y agoYou don't have to make up a name again. The namespace is usually pretty full anyway. If you accept a "break", which is a "break 1", you must consistently also allow a "break 2". Many things are sensitive to refactoring that changes the nesting.
- datalopers 4y agoPHP does this. Works fine, though it’s admittedly rarely needed in the first place.
- jeffybefffy519 4y agoCould just raise StopIteration?
- BiteCode_dev 4y agoThe first loop would catch it, it would not reach the next. But you can indeed use any exeption. However, itertools.product does a better job.
- shusaku 4y agoFYI Fortran has this feature so if you feel like python is really missing this feature try using a more advanced language
- barrkel 4y agoIt's more tedious in C derivatives, where loops and switches share the break keyword. Where that isn't the case, it's much harder to justify multi-level break. Refactor to a function and return instead.
- hk1337 4y agoTwo nested loops is ugly but tolerable, 3+ is disgusting. Refactor it so you don’t have to go 3 deep.
- feet 4y agoWhat method would you use to iterate over a list of lists?
- wrigby 4y agoIt definitely doesn't solve all cases, but quite a lot of nested loops can be eliminated with `itertools.chain`: for item in itertools.chain.from_iterable(list_of_lists): # Do something with item
- hk1337 4y agoA list of lists is two deep, while not preferable two nested loops is probably going to be the solution but if I can come up with another, I would rather use that. Like, perhaps a function to process the row instead of two loops in a single function.
- feet 4y agoIf you have a function with a loop to iterate the inner list and place that function within another loop iterating the outer list, isn't that the same thing? What would you have the inner function do?
- henrydark 4y agoPython has coroutines (in the original sense), so one can implement almost Break-break. For example, it's easy to implement an object so that this will work as expected: from easytowritelibrary import LoopManager lm = LoopManager() for x in lm(a1): for y in lm(a2): if cond: lm.break_(2); break block() It can support stuff like: for x in lm(a1, "a1"): for y in lm(a2, "a2"): if cond1: lm.break_("a1"); break if cond2: lm.continue_("a1"); break block() It can't handle "for-else" correctly, but maybe that's OK.
- benstopics 4y agoThe first thing that came to mind was either for/else, try/except, or refactoring out to a function which were all mentioned in the article. This solves the problem in all cases except a multi-level continue as it was called, and the solution by Python Millionaire was essentially goto labels. The reason high level programming languages moved away from goto labels is because they create spaghetti code. Goto labels were before for and while loops and are not necessary now, but this is a shallow answer because goto labels do work. To dig deeper into why goto labels are bad and the alternative, what goto labels allow you to do is to create a state machine in procedural code. The more explicit way to do this is to create a dictionary object that tracks the state and a while loop with state-based logic that includes a halting condition. But according to Python Millionaire this would not be “Pythonic” which is basically just a blanket term for saying it is built into the language and therefore designed to be simple and easy to read. However, I would argue that goto statements are inherently un-Pythonic because they encourage spaghetti code. I don’t see how for/else or try/except is not Pythonic, but also I don’t see the aversion to refactoring to separate function which is the accepted cross-language way to refactor and simplify code. It even lets you write a built-in comment for what the function does, the function name. That being said, an easy way to determine if the function failed is to either throw and exception or simply return a result like True/False which would determine whether to break out of the parent loop.