Re: [Python-ideas] for/except/else
On Sun, Mar 05, 2017 at 01:17:31PM +1000, Nick Coghlan wrote: > I forget where it came up, but I seem to recall Guido saying that if he > were designing Python today, he wouldn't include the "else:" clause on > loops, since it inevitably confuses folks the first time they see it. Heh, if we exclude all features that confuse people the first time they see it, we'd have to remove threads, Unicode, floating point maths, calls to external processes, anything OS-dependent, metaclasses, classes, ... :-) It took me the longest time to realise that the "else" clause didn't *only* run when the loop sequence is empty. That is, I expected that given: for x in random.choice(["", "a"]): # either empty, or a single item print("run the loop body") else: print("loop sequence is empty") That's because it *seems* to work that way, if you do insufficient testing: for x in []: raise ValueError # dead code is not executed else: print("loop sequence is empty") It is my belief that the problem here is not the else clause itself, but that the name used is misleading. I've seen people other than myself conclude that it means: run the for-loop over the sequence otherwise the sequence is empty, run the ELSE block I've seen people think that it means: set break_seen flag to false run the for-loop if break is executed, set the break_seen flat to true then break if break_seen is false, run the "ELSE NOT BREAK" clause and consequently ask how they can access the break_seen flag for themselves. Presumably they want to write something like: run the for-loop if break_seen is true, do this else (break_seen is false) do that I think that the name "else" here is a "misunderstanding magnet", it leads people to misunderstand the nature of the clause and its implications. For example, I bet that right now there are people reading this and nodding along with me and thinking "maybe we should rename it something more explicit, like "else if no break", completely oblivious to the fact that `break` is NOT the only way to avoid running the `else` clause. I believe that the name should have been "then", not "else". It describes what the code does: run the for-block THEN run the "else" block There's no flag to be tested, and the "else" block simply runs once, after the for-loop, regardless of whether the for-loop runs once or ten times or zero times (empty sequence). To avoid running the "else" ("then") block, you have to exit the entire block of code using: - break - return - raise which will all transfer execution past the end of the for...else (for...then) compound statement. Since I realised that the else block merely runs directly after the for, I've never had any problem with the concept. `break` merely jumps past the for...else block, just as `return` exits the function and `raise` triggers an exception which transfers execution to the surrounding `except` clause. -- Steve ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] for/except/else
On 03.03.2017 09:47, Wolfgang Maier wrote: However, the fact that else exists generates a regrettable asymmetry in that there is direct language support for detecting one outcome, but not the other. Stressing the analogy to try/except/else one more time, it's as if "else" wasn't available for try blocks. You could always use a flag to substitute for it: dealt_with_exception = False try: do_stuff() except: deal_with_exception() dealt_with_exception = True if dealt_with_exception: do_stuff_you_would_do_in_an_else_block() Even worse when we think about the "finally" clause. Regards, Sven ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] for/except/else
On 03/03/2017 04:36 AM, Nick Coghlan wrote: On 2 March 2017 at 21:06, Wolfgang Maier> wrote: - overall I looked at 114 code blocks that contain one or more breaks Thanks for doing that research :) Of the remaining 19 non-trivial cases - 9 are variations of your classical search idiom above, i.e., there's an else clause there and nothing more is needed - 6 are variations of your "nested side-effects" form presented above with debatable (see above) benefit from except break - 2 do not use an else clause currently, but have multiple breaks that do partly redundant things that could be combined in a single except break clause Those 8 cases could also be reviewed to see whether a flag variable might be clearer than relying on nested side effects or code repetition. [...] This is a case where a flag variable may be easier to read than loop state manipulations: may_have_common_prefix = True while may_have_common_prefix: prefix = None for item in items: if not item: may_have_common_prefix = False break if prefix is None: prefix = item[0] elif item[0] != prefix: may_have_common_prefix = False break else: # all subitems start with a common "prefix". # move it out of the branch for item in items: del item[0] subpatternappend(prefix) Although the whole thing could likely be cleaned up even more via itertools.zip_longest: for first_uncommon_idx, aligned_entries in enumerate(itertools.zip_longest(*items)): if not all_true_and_same(aligned_entries): break else: # Everything was common, so clear all entries first_uncommon_idx = None for item in items: del item[:first_uncommon_idx] (Batching the deletes like that may even be slightly faster than deleting common entries one at a time) Given the following helper function: def all_true_and_same(entries): itr = iter(entries) try: first_entry = next(itr) except StopIteration: return False if not first_entry: return False for entry in itr: if not entry or entry != first_entry: return False return True - finally, 1 is a complicated break dance to achieve sth that clearly would have been easier with except break; from typing.py: [...] I think is another case that is asking for the inner loop to be factored out to a named function, not for reasons of re-use, but for reasons of making the code more readable and self-documenting :) It's true that using a flag or factoring out redundant code is always a possibility. Having the except clause would clearly not let people do anything they couldn't have done before. On the other hand, the same is true for the else clause - it's only advantage here is that it's existing already - because a single flag could always distinguish between a break having occurred or not: brk = False for item in iterable: if some_condition: brk = True break if brk: do_stuff_upon_breaking_out() else: do_alternative_stuff() is a general pattern that would always work without except *and* else. However, the fact that else exists generates a regrettable asymmetry in that there is direct language support for detecting one outcome, but not the other. Stressing the analogy to try/except/else one more time, it's as if "else" wasn't available for try blocks. You could always use a flag to substitute for it: dealt_with_exception = False try: do_stuff() except: deal_with_exception() dealt_with_exception = True if dealt_with_exception: do_stuff_you_would_do_in_an_else_block() So IMO the real difference here is that the except clause after for would require adding it to the language, while the else clauses are there already. With that we're back at the high bar for adding new syntax :( A somewhat similar case that comes to mind here is PEP 315 -- Enhanced While Loop, which got rejected for two reasons, the first one being pretty much the same as the argument here, i.e., that instead of the proposed do .. while it's always possible to factor out or duplicate a line of code. However, the second reason was that it required the new "do" keyword, something not necessary for the current suggestion. ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] for/except/else
On 3/1/17, Wolfgang Maierwrote: > - as explained by Nick, the existence of "except break" would strengthen > the analogy with try/except/else and help people understand what the > existing else clause after a loop is good for. I was thinking bout this analogy: 1. try/else (without except) is SyntaxError. And seems useless. 2. try/break/except is backward compatible: for i in L: try: break except Something: pass except break: # current code has not this so break is applied to for-block 3. for/raise/except (which is natural application of this analogy) could reduce indentation but in my personal view that don't improve readability (but I could be wrong) It could help enhance "break" possibilities so "simplify" double break in nested loops. for broken = False for if condition1(): # I like to "double break" here raise SomeError() if condition2(): break except SomeError: break except break: broken = True 4. for/finally may be useful ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] for/except/else
On 1 March 2017 at 06:37, Wolfgang Maierwrote: > Now here's the proposal: allow an except (or except break) clause to follow > for/while loops that will be executed if the loop was terminated by a break > statement. After rethinking over some code I've written in the past, yes, I agree this change could be a nice one. The simple fact that people are commenting that they could chage the code to be an inner function in order to break from nested "for" loops should be a hint this syntax is useful. (I myself have done that with exceptions in some cases). js -><- ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] for/except/else
On 1 March 2017 at 19:37, Wolfgang Maier < wolfgang.ma...@biologie.uni-freiburg.de> wrote: > I know what the regulars among you will be thinking (time machine, high > bar for language syntax changes, etc.) so let me start by assuring you that > I'm well aware of all of this, that I did research the topic before posting > and that this is not the same as a previous suggestion using almost the > same subject line. > > Now here's the proposal: allow an except (or except break) clause to > follow for/while loops that will be executed if the loop was terminated by > a break statement. > > The idea is certainly not new. In fact, Nick Coghlan, in his blog post > http://python-notes.curiousefficiency.org/en/latest/python_ > concepts/break_else.html, uses it to provide a mental model for the > meaning of the else following for/while, but, as far as I'm aware, he never > suggested to make it legal Python syntax. > > Now while it's possible that Nick had a good reason not to do so, I never really thought about it, as I only use the "else:" clause for search loops where there aren't any side effects in the "break" case (other than the search result being bound to the loop variable), so while I find "except break:" useful as an explanatory tool, I don't have any practical need for it. I think you've made as strong a case for the idea as could reasonably be made :) However, Steven raises a good point that this would complicate the handling of loops in the code generator a fair bit, as it would add up to two additional jump targets in cases wherever the new clause was used. Currently, compiling loops only needs to track the start of the loop (for continue), and the first instruction after the loop (for break). With this change, they'd also need to track: - the start of the "except break" clause (for break when the clause is used) - the start of the "else" clause (for the non-break case when both trailing clauses are present) The design level argument against adding the clause is that it breaks the "one obvious way" principle, as the preferred form for search loops look like this: for item in iterable: if condition(item): break else: # Else clause either raises an exception or sets a default value item = get_default_value() # If we get here, we know "item" is a valid reference operation(item) And you can easily switch the `break` out for a suitable `return` if you move this into a helper function: def find_item_of_interest(iterable): for item in iterable: if condition(item): return item # The early return means we can skip using "else" return get_default_value() Given that basic structure as a foundation, you only switch to the "nested side effect" form if you have to: for item in iterable: if condition(item): operation(item) break else: # Else clause neither raises an exception nor sets a default value condition_was_never_true(iterable) This form is generally less amenable to being extracted into a reusable helper function, since it couples the search loop directly to the operation performed on the bound item, whereas decoupling them gives you a lot more flexibility in the eventual code structure. The proposal in this thread then has the significant downside of only covering the "nested side effect" case: for item in iterable: if condition(item): break except break: operation(item) else: condition_was_never_true(iterable) While being even *less* amenable to being pushed down into a helper function (since converting the "break" to a "return" would bypass the "except break" clause). So while it is cool to see this written up as a concrete proposal (thank you!), I don't think it makes the grade as an actual potential syntax change. Cheers, Nick. -- Nick Coghlan | ncogh...@gmail.com | Brisbane, Australia ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] for/except/else
On 03/01/2017 01:37 AM, Wolfgang Maier wrote: Now here's the proposal: allow an except (or except break) clause to follow for/while loops that will be executed if the loop was terminated by a break statement. I find the proposal interesting. More importantly, the proposal is well written and clear -- thank you! -- ~Ethan~ ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Re: [Python-ideas] for/except/else
> On 2017 Mar 1 , at 4:37 a, Wolfgang Maier >wrote: > > I know what the regulars among you will be thinking (time machine, high bar > for language syntax changes, etc.) so let me start by assuring you that I'm > well aware of all of this, that I did research the topic before posting and > that this is not the same as a previous suggestion using almost the same > subject line. > > Now here's the proposal: allow an except (or except break) clause to follow > for/while loops that will be executed if the loop was terminated by a break > statement. > > The idea is certainly not new. In fact, Nick Coghlan, in his blog post > http://python-notes.curiousefficiency.org/en/latest/python_concepts/break_else.html, > uses it to provide a mental model for the meaning of the else following > for/while, but, as far as I'm aware, he never suggested to make it legal > Python syntax. > > Now while it's possible that Nick had a good reason not to do so, I think > there would be three advantages to this: > > - as explained by Nick, the existence of "except break" would strengthen the > analogy with try/except/else and help people understand what the existing > else clause after a loop is good for. > There has been much debate over the else clause in the past, most > prominently, a long discussion on this list back in 2009 (I recommend > interested people to start with Steven D'Aprano's Summary of it at > https://mail.python.org/pipermail/python-ideas/2009-October/006155.html) that > shows that for/else is misunderstood by/unknown to many Python programmers. > I’d like to see some examples where nested for loops couldn’t easily be avoided in the first place. > for n in range(2, 10): >for x in range(2, n): >if n % x == 0: >print(n, 'equals', x, '*', n//x) >break >else: ># loop fell through without finding a factor >print(n, 'is a prime number') Replace the inner loop with a call to any consuming a generator for n in range(2,10): if any(n % x == 0 for x in range(2,n)): print('{} equals {} * {}'.format(n, x, n//x)) else: print('{} is prime'.format(n)) > > - it could provide an elegant solution for the How to break out of two loops > issue. This is another topic that comes up rather regularly (python-list, > stackoverflow) and there is again a very good blog post about it, this time > from Ned Batchelder at > https://nedbatchelder.com/blog/201608/breaking_out_of_two_loops.html. > Stealing his example, here's code (at least) a newcomer may come up with > before realizing it can't work: > > s = "a string to examine" > for i in range(len(s)): >for j in range(i+1, len(s)): >if s[i] == s[j]: >answer = (i, j) >break # How to break twice??? Replace the inner loop with a call to str.find for i, c in enumerate(s): j = s.find(c, i+1) if j >= 0: answer = (i, j) break ___ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/