Re: [Python-ideas] for/except/else

2017-03-04 Thread Steven D'Aprano
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

2017-03-03 Thread Sven R. Kunze

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

2017-03-03 Thread Wolfgang Maier

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

2017-03-02 Thread Pavol Lisy
On 3/1/17, Wolfgang Maier  wrote:

> - 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

2017-03-02 Thread Joao S. O. Bueno
On 1 March 2017 at 06:37, 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.

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

2017-03-01 Thread Nick Coghlan
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

2017-03-01 Thread Ethan Furman

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

2017-03-01 Thread Clint Hepner

> 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/