Loop with else clause

DL Neil PythonList at DancesWithMice.info
Thu Feb 7 03:42:17 EST 2019


Further to our discussion of how to improve a code review's discovery of 
the mistaken handling of a for...else... construct:-


Yesterday was a national holiday, but today gave some opportunity to 
research. Way back in 2009 there was spirited discussion over on the 
Python Ideas list (warning, even the mailing list's index covers 
multiple screen-lengths):

- this confusion is not new by any measure, herewith a list of previous 
occasions "fists were raised concerning for..else."
https://mail.python.org/pipermail/python-ideas/2009-October/006164.html

- an excellent summary of the 2009 debate which offers no less than six 
ways to 'improve' for... else...
https://mail.python.org/pipermail/python-ideas/2009-October/006155.html

- (as mentioned earlier) the BDFL weighed-in a couple of times. His 
regret is: "That's a flaw, and I don't quite know what to do about it. 
It's about 20 years too late to remove or rename it. But we probably 
should not do more of these. That's a lesson."
(OK, so make that thirty years - older than the coder who the 
code-review noticed falling into this coding 'gotcha'!)
https://mail.python.org/pipermail/python-ideas/2009-October/006083.html

- herewith a (rather complicated) suggestion, and critique
https://mail.python.org/pipermail/python-ideas/2009-October/006054.html

- one rather hopeful option (actual words to be used notwithstanding)
	for i in SEQ:
	  A
	except:
	  B
	else:
	  C
appears here:
https://mail.python.org/pipermail/python-ideas/2009-October/006044.html


Somewhat related, PEP 548 proposed an "More Flexible Loop Control". This 
was addressing the confusion caused by break within a loop. It was rejected.


Each of the above addresses issues 'within', that is to say happenings 
during iteration - whether the entire loop or iteration cut-short by a 
break (and thus the idea that "else" might be re-worded to indicate 
'after a break').

However, as mentioned by one contributor, the specific use-case our team 
faced was an issue that arises prior to the loop. Alternately-expressed: 
that according to Python's logic, prevents even a single iteration of 
that loop. Thus, any 'solution' would reside outside of for and while 
statements because they only consider if a loop should continue or 
terminate - not handling the question of whether it should start at all!

PEP 315 is the only discussion (I've found) which looks 'outside' or 
'before' the loop itself. It proposed an "Enhanced While Loop", 
attempting to separate 'setup' or loop control from loop content. It was 
rejected.


So, reading-around brought nothing much useful. Back to the code-face...

Thank you to the several folk who responded with ideas to express/improve:

     if list:
         process_list() #the heading and for-loop, as above
     else:
         print( "Sorry...

NB this is a constructed 'toy example' attempting to be the shortest 
illustration of use-cases and invented purely to communicate the need 
and structure. It was expected to be interpreted as pseudo-python-code. 
(you'd not use/allow "process_list" as the name of a function, would you?)

(With apologies as necessary) one of the dangers of 'toy examples' is 
the reader taking them at face value, instead of as (over-)simplified 
illustrations. In 'real life' the loop code and the no-loop exception 
are both considerably longer than a single line. Accordingly, using a 
function would be a good way to summarise and self-document the 
activity, ie the if statement's two code-blocks would make the whole 
statement very/too-long (readability)!

The "if list:" expression is overly-simplistic. The recommendation of 
"if len(list):" is absolutely sound, for reasons of polymorphism.

In-lieu of a Python construct, there are definitely situations when use 
of a sentinel makes better sense. However, given their risk, in many 
ways Python tries to avoid using such, eg consuming iterators until a 
StopIteration exception is returned. (includes files, is subsumed by 
ContextManagers...), thus "pythonic". That said, the classic use of 
for... else... is in searching for a particular element within an 
iterable which has all the hallmarks of a "sentinel".


Today, kicking ideas around, I coded three other possible 'solutions' 
for the review team's discussions:

One of these involves coding three functions: the decision (yielding a 
boolean), the expected-case, and the unusual-case. The satisfaction in 
this was readability factors with a simple if statement..

Somewhat more complex, and I feel a bit OTT, was to sub-class list() and 
write a method which would indicate an empty list, plus include much of 
both the expected-case and the empty-list methods therein. Somehow 
"class ListShouldNotBeEmpty" doesn't seem a catchy (if descriptive) 
title - however, it works! The decision 'function' could also/then be 
made @property and (perhaps) thus contribute to readability.

Lastly, I 'remembered' conditional expressions. Thus, furthering the 
idea of two/three functions:

     process_list() if list_has_contents() \
			else process_empty_list()

Report card:
Functional = yes.
Pythonic = yes.
Readable = much debated/relates to the expertise of the coder/reader


Your thoughts?
-- 
Regards =dn



More information about the Python-list mailing list