Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

for / while else doesn't make sense

831 views
Skip to first unread message

Herkermer Sherwood

unread,
May 19, 2016, 12:43:56 PM5/19/16
to
Most keywords in Python make linguistic sense, but using "else" in for and
while structures is kludgy and misleading. I am under the assumption that
this was just utilizing an already existing keyword. Adding another like
"andthen" would not be good.

But there is already a reserved keyword that would work great here.
"finally". It is already a known keyword used in try blocks, but would work
perfectly here. Best of all, it would actually make sense.

Unfortunately, it wouldn't follow the semantics of try/except/else/finally.

Is it better to follow the semantics used elsewhere in the language, or
have the language itself make sense semantically?

I think perhaps "finally" should be added to for and while to do the same
thing as "else". What do you think?

Ian Kelly

unread,
May 19, 2016, 1:03:24 PM5/19/16
to
On Thu, May 19, 2016 at 10:31 AM, Herkermer Sherwood <the...@gmail.com> wrote:
> Most keywords in Python make linguistic sense, but using "else" in for and
> while structures is kludgy and misleading. I am under the assumption that
> this was just utilizing an already existing keyword. Adding another like
> "andthen" would not be good.

"else" makes sense from a certain point of view, but I think that
logic may not be communicated well. At the start of each loop
iteration, the loop construct makes a test for whether the loop should
continue or not. If that test ever fails (i.e. if the condition of the
while loop is false), the else block is executed instead. So you can
think of it as a repeated if-else where the else block has the
additional effect of exiting the loop.

> But there is already a reserved keyword that would work great here.
> "finally". It is already a known keyword used in try blocks, but would work
> perfectly here. Best of all, it would actually make sense.
>
> Unfortunately, it wouldn't follow the semantics of try/except/else/finally.

"finally" in exception handling denotes a block that is *always*
executed. Using it for a block that is only sometimes executed would
dilute that meaning.

Ned Batchelder

unread,
May 19, 2016, 1:22:31 PM5/19/16
to
For/else has always caused people consternation.

My best stab at explaining it is this: the else clause is executed if no
break was encountered in the body of the for loop. A simple structure
would look like this:

for thing in container:
if something_about(thing):
# Found it!
do_something(thing)
break
else:
# Didn't find it..
no_such_thing()

I think of the "else" as being paired with the "if" inside the loop.
At run time, you execute a number of "if"s, one for each iteration
around the loop. The "else" is what gets executed if none of the
"if"s was true. In that sense, it's exactly the right keyword to
use.

--Ned.

Steven D'Aprano

unread,
May 19, 2016, 1:47:10 PM5/19/16
to
On Fri, 20 May 2016 02:31 am, Herkermer Sherwood wrote:

> Most keywords in Python make linguistic sense, but using "else" in for and
> while structures is kludgy and misleading. I am under the assumption that
> this was just utilizing an already existing keyword. Adding another like
> "andthen" would not be good.

If I could steal the keys to Guido's time machine, I would go back in time
and change the for...else and while...else keywords to for...then and
while...then.

Alas, we missed the opportunity for a major backwards-incompatible change.
Python 3.0 is long past, we're up to 3.5 now, 3.6 is in alpha, there's no
way the keyword is going to be changed :-(


> But there is already a reserved keyword that would work great here.
> "finally". It is already a known keyword used in try blocks, but would
> work perfectly here. Best of all, it would actually make sense.

No. for...else doesn't operate like a finally block. The idea of finally is
that it executes no matter what happens[1]. That's completely the opposite
of for...else: the whole point of for...else is that "break" will jump out
of the block without executing the else part.

> Unfortunately, it wouldn't follow the semantics of
> try/except/else/finally.

Exactly.


> Is it better to follow the semantics used elsewhere in the language, or
> have the language itself make sense semantically?

If Python was a younger language with fewer users, less existing code, and
no backwards-compatibility guarantees, I would argue for changing
for...else to for...then. But Python is over 20 years old, has tens or
hundreds of thousands of users, tens of millions of lines of code, and
quite strict backwards-compatibility guarantees.


> I think perhaps "finally" should be added to for and while to do the same
> thing as "else". What do you think?

I think adding "finally" as an alias is just needlessly confusing.

Think of the millions of people who aren't English speakers who nevertheless
had to memorise weird and unintuitive words like "for", "while", "if" etc.
The least we English speakers can do is suck it up and memorise *one* weird
case, "else".






[1] Well, *almost* no matter what. If you pull the power from the computer,
the finally block never gets a chance to run.


--
Steven

Jon Ribbens

unread,
May 19, 2016, 1:58:49 PM5/19/16
to
On 2016-05-19, Steven D'Aprano <st...@pearwood.info> wrote:
> On Fri, 20 May 2016 02:31 am, Herkermer Sherwood wrote:
>> Most keywords in Python make linguistic sense, but using "else" in for and
>> while structures is kludgy and misleading. I am under the assumption that
>> this was just utilizing an already existing keyword. Adding another like
>> "andthen" would not be good.
>
> If I could steal the keys to Guido's time machine, I would go back in time
> and change the for...else and while...else keywords to for...then and
> while...then.

I guess we should thank our lucky stars that you don't have a time
machine then, since that change would very much be one for the worse
in my opinion. for...else is perfectly straightforward and clearly
the right keywords to use. for...then would be entirely wrong.

Steven D'Aprano

unread,
May 19, 2016, 2:02:39 PM5/19/16
to
On Fri, 20 May 2016 03:22 am, Ned Batchelder wrote:

> On Thursday, May 19, 2016 at 12:43:56 PM UTC-4, Herkermer Sherwood wrote:
>> Most keywords in Python make linguistic sense, but using "else" in for
>> and while structures is kludgy and misleading. I am under the assumption
>> that this was just utilizing an already existing keyword. Adding another
>> like "andthen" would not be good.
>>
>> But there is already a reserved keyword that would work great here.
>> "finally". It is already a known keyword used in try blocks, but would
>> work perfectly here. Best of all, it would actually make sense.
>>
>> Unfortunately, it wouldn't follow the semantics of
>> try/except/else/finally.
>>
>> Is it better to follow the semantics used elsewhere in the language, or
>> have the language itself make sense semantically?
>>
>> I think perhaps "finally" should be added to for and while to do the same
>> thing as "else". What do you think?
>
> For/else has always caused people consternation.
>
> My best stab at explaining it is this: the else clause is executed if no
> break was encountered in the body of the for loop.

You're describing the consequences of the break: it breaks out of the entire
for statement, which includes the (badly named) "else" clause.

It's also misleading: break is not the only way to skip the else clause. You
can also return, or raise.

As far as I can determine, if you consider all the corner cases, the best
description of the behaviour is:

- the "else" clause is UNCONDITIONALLY executed after the for-loop has
completed, but before any code past the for...else blocks gets to run.

- anything which breaks outside of the for...else will prevent execution of
the else clause. returns jumps all the way out of the function, break
doesn't jump out of the function but it jumps out of the combined
for...else block.



> A simple structure would look like this:
>
> for thing in container:
> if something_about(thing):
> # Found it!
> do_something(thing)
> break
> else:
> # Didn't find it..
> no_such_thing()
>
> I think of the "else" as being paired with the "if" inside the loop.

The fatal flaw with that is that there is no requirement that there be any
such "if" inside the loop. In fact, there's no need to even have a break in
the loop at all!


> At run time, you execute a number of "if"s, one for each iteration

"One"?

for x in sequence:
if condition: break
if x < 0: break
if today is Tuesday: break
if i.feel_like_it(): break
else:
print("which `if` matches this `else`?")


> around the loop. The "else" is what gets executed if none of the
> "if"s was true. In that sense, it's exactly the right keyword to
> use.

So long as you don't think too hard about what's actually going on, and only
consider the most obvious case, it almost makes a bit of sense :-)

But really, the else clause *actually is* an unconditional block which
executes after the loop. That's how it is implemented, and that's how it is
best understood.




--
Steven

the...@gmail.com

unread,
May 19, 2016, 2:47:46 PM5/19/16
to
This is exactly what I'm referencing. We can do mental gymnastics for it to make sense, but remove the `break` in your code and what happens? The logic goes away. The code ends up executing anyway, which is what makes it more like "finally" to me. Although, as Ian pointed out that would cause breakages for the "finally" meaning, because with the break you wouldn't expect it to execute, but we expect "finally" always to execute.

It is a great example for sure, but it only makes sense in specific constructs. I think this is a really interesting topic to which there may not be a better answer.

Chris Angelico

unread,
May 19, 2016, 4:01:48 PM5/19/16
to
On Fri, May 20, 2016 at 3:46 AM, Steven D'Aprano <st...@pearwood.info> wrote:
> The idea of finally is
> that it executes no matter what happens[1].
>
> [1] Well, *almost* no matter what. If you pull the power from the computer,
> the finally block never gets a chance to run.

Nor if you kill -9 the process, or get into an infinite loop, or any
number of other things. Specifically, what the finally block
guarantees is that it will be executed *before any code following the
try block*. In this example:

try:
code1
except Exception:
code2
else:
code3
finally:
code4
code5

Once you hit code1, you are absolutely guaranteed that code5 *will
not* be run prior to code4.

ChrisA

Ian Kelly

unread,
May 19, 2016, 4:12:39 PM5/19/16
to
On Thu, May 19, 2016 at 2:01 PM, Chris Angelico <ros...@gmail.com> wrote:
> On Fri, May 20, 2016 at 3:46 AM, Steven D'Aprano <st...@pearwood.info> wrote:
>> The idea of finally is
>> that it executes no matter what happens[1].
>>
>> [1] Well, *almost* no matter what. If you pull the power from the computer,
>> the finally block never gets a chance to run.
>
> Nor if you kill -9 the process, or get into an infinite loop, or any
> number of other things. Specifically, what the finally block
> guarantees is that it will be executed *before any code following the
> try block*. In this example:
>
> try:
> code1
> except Exception:
> code2
> else:
> code3
> finally:
> code4
> code5
>
> Once you hit code1, you are absolutely guaranteed that code5 *will
> not* be run prior to code4.

The guarantee is stronger than that. It's possible to exit the try
block without passing execution to code5 at all. The finally block is
still guaranteed to be executed in this case.

Chris Angelico

unread,
May 19, 2016, 4:28:05 PM5/19/16
to
On Fri, May 20, 2016 at 6:11 AM, Ian Kelly <ian.g...@gmail.com> wrote:
> On Thu, May 19, 2016 at 2:01 PM, Chris Angelico <ros...@gmail.com> wrote:
>> On Fri, May 20, 2016 at 3:46 AM, Steven D'Aprano <st...@pearwood.info> wrote:
>>> The idea of finally is
>>> that it executes no matter what happens[1].
>>>
>>> [1] Well, *almost* no matter what. If you pull the power from the computer,
>>> the finally block never gets a chance to run.
>>
>> Nor if you kill -9 the process, or get into an infinite loop, or any
>> number of other things. Specifically, what the finally block
>> guarantees is that it will be executed *before any code following the
>> try block*. In this example:
>>
>> try:
>> code1
>> except Exception:
>> code2
>> else:
>> code3
>> finally:
>> code4
>> code5
>>
>> Once you hit code1, you are absolutely guaranteed that code5 *will
>> not* be run prior to code4.
>
> The guarantee is stronger than that. It's possible to exit the try
> block without passing execution to code5 at all. The finally block is
> still guaranteed to be executed in this case.

Yes, but I don't know how to depict "other code outside of the try
block" in a way that doesn't fall foul of other nitpicks like "well,
you could call that function from within the try block" :)

ChrisA

Marko Rauhamaa

unread,
May 19, 2016, 4:28:40 PM5/19/16
to
the...@gmail.com:

> This is exactly what I'm referencing. We can do mental gymnastics for
> it to make sense,

Programming languages are not English. Any resemblance is purely
coincidental.


Marko

David Jardine

unread,
May 19, 2016, 5:25:08 PM5/19/16
to
On Thu, May 19, 2016 at 11:47:28AM -0700, the...@gmail.com wrote:
> This is exactly what I'm referencing. We can do mental gymnastics for it
> to make sense, but remove the `break` in your code and what happens? The
> logic goes away.

Quite. What would the code mean? Why would you use "else" if you were
always going to drop out of the end of the loop anyway? You need it in
a situation where you're "hoping" to find something: meet the 6.30 train
at the station, look at each passenger that gets off and if one is
wearing a pink carnation in his/her buttonhole go off together and live
happily ever after. But when the last passenger has got off and you
haven't seen a pink carnation? That's where the "else" clause comes in.
You hope you won't need it.

> [...]

> On Thursday, May 19, 2016 at 10:22:31 AM UTC-7, Ned Batchelder wrote:
> >
> > [...]
> >
> > For/else has always caused people consternation.
> >
> > My best stab at explaining it is this: the else clause is executed if no
> > break was encountered in the body of the for loop. A simple structure
> > would look like this:
> >
> > for thing in container:
> > if something_about(thing):
> > # Found it!
> > do_something(thing)
> > break
> > else:
> > # Didn't find it..
> > no_such_thing()
> >
> > I think of the "else" as being paired with the "if" inside the loop.
> > At run time, you execute a number of "if"s, one for each iteration
> > around the loop. The "else" is what gets executed if none of the
> > "if"s was true. In that sense, it's exactly the right keyword to
> > use.
> >

Cheers,
David

Gregory Ewing

unread,
May 19, 2016, 7:52:11 PM5/19/16
to
Herkermer Sherwood wrote:
> But there is already a reserved keyword that would work great here.
> "finally".
>
> Unfortunately, it wouldn't follow the semantics of try/except/else/finally.
>
> Is it better to follow the semantics used elsewhere in the language, or
> have the language itself make sense semantically?

I think using "finally" this way would be more confusing
than the status quo. Currently, if you see a "finally"
somewhere, it means the block following is always executed
come what may. But with this change, you would need to
look up some arbitrary distance to see whether it belonged
to a "try" or a "for".

It's not so bad with "else" because you need to look back
to find out what condition the "else" refers to anyway.

--
Greg
\

Steven D'Aprano

unread,
May 19, 2016, 8:06:27 PM5/19/16
to
On Fri, 20 May 2016 03:55 am, Jon Ribbens wrote:

> I guess we should thank our lucky stars that you don't have a time
> machine then, since that change would very much be one for the worse
> in my opinion. for...else is perfectly straightforward and clearly
> the right keywords to use. for...then would be entirely wrong.

"Entirely" wrong? "Clearly" the right keyword? "Perfectly" straightforward?

They are extremely strong words given the posts where *even the defenders*
of "else" have admitted that it is a hard keyword to understand. But that's
okay. Maybe you've thought of something the rest of us haven't, and have an
entire consistent mental model of for...else that is easy to understand and
makes it "perfectly straightforward and clearly the right keyword".

Can you explain your model which makes "else" appropriate?

In my experience, some people (including me) misunderstand "for...else" to
mean that the else block runs if the for block *doesn't*. It took me the
longest time to understand why this didn't work as I expected:

for x in seq:
pass
else:
print("seq is empty")

because like many people, my mental model was "the for block runs, OR ELSE
the else block runs". This *incorrect* model seems like it works: if you
set seq=[], say, it prints "seq is empty" as expected.

But its wrong: set seq=[1, 2, 3], and it *still* prints "seq is empty". My
model of what was going on was faulty.

I never would have thought of that model if it had been called "then":

for x in seq:
pass
then:
print("executes after the for block completes")

which describes what actually happens: regardless of whether seq is empty or
not, the loop runs, THEN the "else" block unconditionally executes. That
has the advantage of also matching the implementation, both as byte-code,
and in C.

I've seen people assume that the for loop actually sets a hidden flag to
record whether or not a break was executed. There is no flag. It simply
doesn't get executed because code execution jumps past it.

But please do explain your execution model of "for...else". If it is better
than mine, I'll be happy to use it instead.



--
Steven

gst

unread,
May 19, 2016, 10:02:20 PM5/19/16
to
I know this does not bring anything valuable but:

Definitively agree with your mental model !! 'then' and only "then" is the best keyword in the situation which is on the table right now.

it totally fix the confusing "else" actual mess (for at least 2 reasons).

Python 4.0 ? My son will thank us !

NB:
- I'm not a native English speaker
- but I'm using the "(for,while)/else" way sometimes, but damn the "then" keyword is at least better here !

Bob Martin

unread,
May 20, 2016, 2:45:45 AM5/20/16
to
Yes. "else" and "then" have opposite meanings.

Stephen Hansen

unread,
May 20, 2016, 2:54:13 AM5/20/16
to
On Thu, May 19, 2016, at 07:02 PM, gst wrote:
> Python 4.0 ? My son will thank us !

No, he won't, because while Python 4.0 will happen, likely after Python
3.9, it will not be a major backwards compatible breaking point.

Some people infer that because 3.0 was, 4.0, 5.0, 6.0 are open to it.
They aren't.

--
Stephen Hansen
m e @ i x o k a i . i o

Erik

unread,
May 20, 2016, 4:12:44 AM5/20/16
to
On 20/05/16 00:51, Gregory Ewing wrote:
> It's not so bad with "else" because you need to look back
> to find out what condition the "else" refers to anyway.

With my tongue only slightly in my cheek, if it was desirable to
"fix"/clarify this syntax then I would suggest adding some optional
(existing) trailing keywords to 'else' in this context that spells it out:

for item in seq:
if foo(item):
break
else if not break:
nomatch()

I would rule out "elif not break" - this is just about adding additional
trailing words to existing syntax to spell it out (which might be a good
teaching aid as well as turn into personal preferred style).

I guess that it _could_ be a syntax error to reference "break" if the
for/while loop does not contain the keyword in its body - this could
actually address one of the "confusing" things mentioned in this thread,
like:

seq = []
for item in seq:
pass
else if not break:
pass

# Syntax error - "break" referenced in "for/else" clause but not present
in loop body.


Someone also pointed out that there are other flow control mechanisms
that could prevent the 'else' from being executed: "return" and "raise".
One could extend the above to allow one or more of those to be specified:

else if not break or return or raise:

That looks like a lot of noise, but again it could help make code more
explicit (both to the human reader, and to a compile-time check):

for item in seq:
if foo(item):
break
if bar(item):
return
else if not return:
pass

# Syntax error - extended "for/else" clause does not reference "break",
which exists in loop body.

I would _not_ suggest that the above should ever mean "if it doesn't
return, this code is executed regardless of whether 'break' happened" -
one would remove the 'else' clause altogether for that.

"raise" is more problematic as an exception can always be raised by
pretty much _anything_ in the body without the keyword being present.
Perhaps "or raise" is just a completely benign, optional keyword for the
completists. Or perhaps it's simply not mentioned at all and is always
implied (as is true of exceptions in other constructs such as plain
"if/else", and just generally).


The additional keywords would effectively just be a static checked
filter - the original bare "else" would effectively mean "else if not
any_flow_control_out_of_the_loop" (which is what it means today).


In summary: Adding the "if not" extension is the suggestion, with
"break" as the filter. "return" as an additional filter is a possibility
which may add something, "raise" as an additional filter is a
possibility which probably doesn't add anything (and may actually be
distracting).



I'm not entirely sure if _I_ like this yet (there are some things I've
suggested that I like and some I'm not sure about and some I don't like
but I've mentioned them anyway) - I'm just throwing it out there ;)

E.

Jon Ribbens

unread,
May 20, 2016, 7:59:24 AM5/20/16
to
On 2016-05-20, Steven D'Aprano <st...@pearwood.info> wrote:
> On Fri, 20 May 2016 03:55 am, Jon Ribbens wrote:
>> I guess we should thank our lucky stars that you don't have a time
>> machine then, since that change would very much be one for the worse
>> in my opinion. for...else is perfectly straightforward and clearly
>> the right keywords to use. for...then would be entirely wrong.
>
> "Entirely" wrong? "Clearly" the right keyword? "Perfectly" straightforward?
>
> They are extremely strong words given the posts where *even the defenders*
> of "else" have admitted that it is a hard keyword to understand. But that's
> okay. Maybe you've thought of something the rest of us haven't, and have an
> entire consistent mental model of for...else that is easy to understand and
> makes it "perfectly straightforward and clearly the right keyword".
>
> Can you explain your model which makes "else" appropriate?

Certainly. "else:" is (almost?) invariably used in the situation where
you are iterating through something in order to find a value which
matches a certain condition. So the "for:" block means "find this
value" and the "else:" means "else do this".

> In my experience, some people (including me) misunderstand "for...else" to
> mean that the else block runs if the for block *doesn't*. It took me the
> longest time to understand why this didn't work as I expected:
>
> for x in seq:
> pass
> else:
> print("seq is empty")
>
> because like many people, my mental model was "the for block runs, OR ELSE
> the else block runs". This *incorrect* model seems like it works: if you
> set seq=[], say, it prints "seq is empty" as expected.
>
> But its wrong: set seq=[1, 2, 3], and it *still* prints "seq is empty". My
> model of what was going on was faulty.

The problem there is that the mental model is *completely* wrong.
"else:" has nothing at all to do with the number of values in the
iterated sequence.

> I never would have thought of that model if it had been called "then":
>
> for x in seq:
> pass
> then:
> print("executes after the for block completes")

I would find that very confusing. "then:" makes it sound like
executing that block is the usual case, when in practice it is
usually the exception - the fallback code if the expected value
was not found.

Michael Selik

unread,
May 20, 2016, 11:33:31 AM5/20/16
to
On Thu, May 19, 2016 at 1:04 PM Ian Kelly <ian.g...@gmail.com> wrote:

> On Thu, May 19, 2016 at 10:31 AM, Herkermer Sherwood <the...@gmail.com>
> wrote:
> > Most keywords in Python make linguistic sense, but using "else" in for
> and
> > while structures is kludgy and misleading. I am under the assumption that
> > this was just utilizing an already existing keyword. Adding another like
> > "andthen" would not be good.
>
> "else" makes sense from a certain point of view, but I think that
> logic may not be communicated well. At the start of each loop
> iteration, the loop construct makes a test for whether the loop should
> continue or not. If that test ever fails (i.e. if the condition of the
> while loop is false), the else block is executed instead. So you can
> think of it as a repeated if-else where the else block has the
> additional effect of exiting the loop.
>
> > But there is already a reserved keyword that would work great here.
> > "finally". It is already a known keyword used in try blocks, but would
> work
> > perfectly here. Best of all, it would actually make sense.
> >
> > Unfortunately, it wouldn't follow the semantics of
> try/except/else/finally.
>
> "finally" in exception handling denotes a block that is *always*
> executed. Using it for a block that is only sometimes executed would
> dilute that meaning.
>

It's unfortunate that so many people responded so quickly, since Ian's
explanation was so clear (I thought). For further clarity, I'll write out
the implicit if-statement that Ian described, though in my case it'd be at
the end of the block, somewhat like a do-while:

IF keep_looping()
THEN GOTO LOOP_START
ELSE GOTO LOOP_COMPLETED

Also, Nick Coghlan has a good post about this (
http://python-notes.curiousefficiency.org/en/latest/python_concepts/break_else.html
)

Zachary Ware

unread,
May 20, 2016, 12:00:38 PM5/20/16
to
On Fri, May 20, 2016 at 3:09 AM, Erik <pyt...@lucidity.plus.com> wrote:
> On 20/05/16 00:51, Gregory Ewing wrote:
>>
>> It's not so bad with "else" because you need to look back
>> to find out what condition the "else" refers to anyway.
>
>
> With my tongue only slightly in my cheek, if it was desirable to
> "fix"/clarify this syntax then I would suggest adding some optional
> (existing) trailing keywords to 'else' in this context that spells it out:
>
> for item in seq:
> if foo(item):
> break
> else if not break:
> nomatch()

With tongue firmly cheeked, you can always use the special `:#` operator:

for item in seq:
if foo(item):
break
else:# if no break:
nomatch()

This has the benefit that you can use whatever syntax you like after
the `:#`, and use it in any version of Python you want.

--
Zach

Christopher Reimer

unread,
May 20, 2016, 4:21:19 PM5/20/16
to
On 5/20/2016 8:59 AM, Zachary Ware wrote:

> On Fri, May 20, 2016 at 3:09 AM, Erik <pyt...@lucidity.plus.com> wrote:
>> On 20/05/16 00:51, Gregory Ewing wrote:
>>> It's not so bad with "else" because you need to look back
>>> to find out what condition the "else" refers to anyway.
>>
>> With my tongue only slightly in my cheek, if it was desirable to
>> "fix"/clarify this syntax then I would suggest adding some optional
>> (existing) trailing keywords to 'else' in this context that spells it out:
>>
>> for item in seq:
>> if foo(item):
>> break
>> else if not break:
>> nomatch()
> With tongue firmly cheeked, you can always use the special `:#` operator:
>
> for item in seq:
> if foo(item):
> break
> else:# if no break:
> nomatch()
>
> This has the benefit that you can use whatever syntax you like after
> the `:#`, and use it in any version of Python you want.

According to "Effective Python: 59 Specific Ways to Write Better Python"
by Brett Slatkin, Item 12 recommends against using the else block after
for and while loops (see page 25): "Avoid using else blocks after loops
because their behavior isn't intuitive and can be confusing."

Until I read the book, I wasn't aware of this feature (or bug). Doesn't
seem like a feature I would use since it's not commonly found in other
programming languages. As the author demonstrates in his book, I would
probably write a helper function instead.

Item 13 does recommend using the else block for try/except/else/finally
in exception handling. :)

Thank you,

Chris R.

Steven D'Aprano

unread,
May 20, 2016, 6:43:58 PM5/20/16
to
On Sat, 21 May 2016 05:20 am, Christopher Reimer wrote:

> According to "Effective Python: 59 Specific Ways to Write Better Python"
> by Brett Slatkin, Item 12 recommends against using the else block after
> for and while loops (see page 25): "Avoid using else blocks after loops
> because their behavior isn't intuitive and can be confusing."

By that logic, we ought to:

- avoid using floats because their behaviour isn't intuitive and
can be confusing;
- avoid using lists because their behaviour isn't intuitive and
can be confusing;
- avoid using classes because their behaviour isn't intuitive and
can be confusing;
- avoid any form of asynchronous functions because their behaviour
isn't intuitive and can be confusing;

and so on. I can give examples of unintuitive and confusing behaviour for
all of those things, and more, except the last. And the reason I can't give
examples for async programming is because it confuses me and I don't
understand it well enough to give even a minimal example.

Just about the only things in Python which are intuitive and not confusing
to somebody are None and ints.

Or, we can *learn how to use the features* and stop thinking that
programming is a matter of intuition. And most importantly, stop thinking
that features need to be judged entirely by the least knowledgeable
programmers.


> Until I read the book, I wasn't aware of this feature (or bug). Doesn't
> seem like a feature I would use since it's not commonly found in other
> programming languages. As the author demonstrates in his book, I would
> probably write a helper function instead.

Sorry, was that called "Ineffective Python"?

There is absolutely nothing effective about re-inventing the wheel badly or
writing unnecessary code.

Are you programming in those other languages or in Python? If you're
programming in, say, Javascript, I can completely understand you deciding
to limit yourself to features available in Javascript. Indeed to try to use
Python language features in Javascript would be an exercise in frustration.
Likewise using Ruby language features in Python is nothing but SyntaxError
after SyntaxError, it makes it hard to get work done.

But the idea that you should avoid a Python feature while programming in
Python because Javascript doesn't have it, or Ruby, or C, is surely the
height of muddleheaded thinking. You're not programming Javascript, Ruby or
C, you're programming in Python. The whole point of picking one language
over another is to get access to the tools and features that language
offers. Otherwise you're just wasting your time.



--
Steven

the...@gmail.com

unread,
May 20, 2016, 7:24:25 PM5/20/16
to
You seem to have missed the point. Nobody is suggesting, I don't believe, that all of a language should be intuitive. Rather that if any part of it is unnecessarily counter-intuitive, it may be worth looking for a better solution. Python is a very well designed language when it comes to in linguistic presentation. In this case however, it is not only unintuitive but counter-intuitive.

The original question was simply, "Is it better to follow the semantics used elsewhere in the language, or have the language itself make sense semantically?" So, it is better to leave counter-intuitive constructs so they are consistent across the language or try to make each keyword make semantic sense where it is used?

Ethan Furman

unread,
May 20, 2016, 7:58:05 PM5/20/16
to
On 05/20/2016 04:55 AM, Jon Ribbens wrote:
> On 2016-05-20, Steven D'Aprano wrote:
>> On Fri, 20 May 2016 03:55 am, Jon Ribbens wrote:

>>> I guess we should thank our lucky stars that you don't have a time
>>> machine then, since that change would very much be one for the worse
>>> in my opinion. for...else is perfectly straightforward and clearly
>>> the right keywords to use. for...then would be entirely wrong.
>>
>> "Entirely" wrong? "Clearly" the right keyword? "Perfectly" straightforward?
>>
>> They are extremely strong words given the posts where *even the defenders*
>> of "else" have admitted that it is a hard keyword to understand. But that's
>> okay. Maybe you've thought of something the rest of us haven't, and have an
>> entire consistent mental model of for...else that is easy to understand and
>> makes it "perfectly straightforward and clearly the right keyword".
>>
>> Can you explain your model which makes "else" appropriate?
>
> Certainly. "else:" is (almost?) invariably used in the situation where
> you are iterating through something in order to find a value which
> matches a certain condition. So the "for:" block means "find this
> value" and the "else:" means "else do this".

I'm happy that you have a working mental model for for/else (seriously,
I am) but please don't discount the confusion and consternation for the
many folks who don't start with that mental model.

The number of times I have /wanted/ to use the for/else structure for
searching is small (and I remember them both ;) -- every other time what
I wanted was an _else_ that ran iff the iterable was already empty when
the _for_ encountered it.


>> because like many people, my mental model was "the for block runs, OR ELSE
>> the else block runs". This *incorrect* model seems like it works: if you
>> set seq=[], say, it prints "seq is empty" as expected.

> The problem there is that the mental model is *completely* wrong.

D'oh. Completely wrong, but easy to guess because of the similarity
with if/else.


>> I never would have thought of that model if it had been called "then":
>>
>> for x in seq:
>> pass
>> then:
>> print("executes after the for block completes")
>
> I would find that very confusing. "then:" makes it sound like
> executing that block is the usual case, when in practice it is
> usually the exception - the fallback code if the expected value
> was not found.

If you don't take the extra step of _break_ it is the usual case. Most
of my for loops always run all the way through, so why have an else?
Two possible reasons:

- the loop didn't run at all
- the loop didn't run all the way

Guido picked the one that was obvious to him (him being Dutch and all ;) .

I just read Nick's post about it, and while it helps, I think the
syntactic similarity between try/else and for/else (did the block run
without error) is dwarfed by the semantic difference: a try/else else
block runs if nothing /bad/ happened whereas a for/else else block runs
if something bad /did/ happen; to wit, the thing you were looking for
was not found (or your loop didn't run at all ;) .

But as others have said, this isn't going to change now, and I'm okay
with that. But, please, be a bit more understanding of those who don't
immediately grok the for/else and while/else loops.

--
~Ethan~

Ben Finney

unread,
May 20, 2016, 8:22:02 PM5/20/16
to
Steven D'Aprano <st...@pearwood.info> writes:

> Just about the only things in Python which are intuitive and not
> confusing to somebody are None and ints.

I'll go even further:

* The behaviour of ‘int’ is confusing to some. For example, to those who
expect integers to produce fractions when divided.

* The behaviour of ‘None’ is confusing to some. For example, to those
who expect it to be a non-value, with no type.

Examples of both those have appeared in this very forum in recent years.

So, I think we can assert with confidence: if “avoid bevause it is
unintuitive and confusing to some newcomers” were a good reason to avoid
a Python feature, *all* Python features would be subject to oblivion.

> Or, we can *learn how to use the features* and stop thinking that
> programming is a matter of intuition. And most importantly, stop
> thinking that features need to be judged entirely by the least
> knowledgeable programmers.

Yes. Appeals to intuition are irrelevant in talking about language
features, IMO.

The intuitions of newcomers should weigh heavily in language design.
That is *not* the same as appealing to intuition: intuition gives you
none of the essential and difficult concepts necessary to programming.

> But the idea that you should avoid a Python feature while programming
> in Python because Javascript doesn't have it, or Ruby, or C, is surely
> the height of muddleheaded thinking. You're not programming
> Javascript, Ruby or C, you're programming in Python.

This is not to say that every Python feature can be used without
concern. Steven is not arguing that avoidance of a feature is never
justified.

Rather, he's demonstrating that *that particular justification* is void.

There may be a good reason to avoid Python behaviour Foo, but “because
JavaScript/Ruby/Lisp/BASIC/INTERCAL doesn't have behaviour Foo” is not a
valid justification. The case must be argued on other merits, if any.

--
\ “Special today: no ice cream.” —mountain inn, Switzerland |
`\ |
_o__) |
Ben Finney

Jon Ribbens

unread,
May 20, 2016, 8:28:40 PM5/20/16
to
On 2016-05-20, Ethan Furman <et...@stoneleaf.us> wrote:
> On 05/20/2016 04:55 AM, Jon Ribbens wrote:
>> Certainly. "else:" is (almost?) invariably used in the situation where
>> you are iterating through something in order to find a value which
>> matches a certain condition. So the "for:" block means "find this
>> value" and the "else:" means "else do this".
>
> I'm happy that you have a working mental model for for/else (seriously,
> I am) but please don't discount the confusion and consternation for the
> many folks who don't start with that mental model.
>
> The number of times I have /wanted/ to use the for/else structure for
> searching is small (and I remember them both ;) -- every other time what
> I wanted was an _else_ that ran iff the iterable was already empty when
> the _for_ encountered it.

Well that's not a circumstance that's appropriate for the construct.
Saying you want a different construct entirely is an entirely
different argument.

>> I would find that very confusing. "then:" makes it sound like
>> executing that block is the usual case, when in practice it is
>> usually the exception - the fallback code if the expected value
>> was not found.
>
> If you don't take the extra step of _break_ it is the usual case.

Having an "for: else:" clause without a "break" would be so unusual
that it's literally nonexistent, because it would always be a bug.
So no, it isn't the usual case for "for: else:".

> But as others have said, this isn't going to change now, and I'm okay
> with that. But, please, be a bit more understanding of those who don't
> immediately grok the for/else and while/else loops.

You're misunderstanding me. I'm not saying that the meaning of
"for: else:" is 100% intuitively obvious. I'm saying that it's
*more* obvious than it would be if it used any of the other existing
keywords. I suppose I'm also saying that there isn't any other
obvious word that could be made into a keyword that would be better
than "else" (even if we assumed that adding a new keyword was a
cost-free exercise).

Jon Ribbens

unread,
May 20, 2016, 8:39:37 PM5/20/16
to
On 2016-05-20, Steven D'Aprano <st...@pearwood.info> wrote:
> By that logic, we ought to:
>
> - avoid using floats because their behaviour isn't intuitive and
> can be confusing;

To be fair, I'm very sympathetic to that argument. I think programming
languages should never magically produce floats out of nowhere unless
the programmer has explicitly done "import float" or "float('3.23')"
or somesuch. They're misunderstood so often that any convenience
they provide is outweighed by the danger they bring.

"(1/10) * (1/10) * 10 != (1/10)" anyone? I was distinctly unhappy with
the Python 3 "2/3 ~= 0.6666" thing and regard it as a very retrograde
change.

Chris Angelico

unread,
May 20, 2016, 10:05:28 PM5/20/16
to
The trouble is, what SHOULD 2/3 return?

* An integer? Makes a lot of sense to a C programmer. Not so much to
someone who is expecting a nonzero value. This isn't terrible (hey,
Python 2 managed with it no problem), but will definitely confuse a
number of people.

* A float? That's what we currently have. Not perfect, but it's going
to confuse less people than 0 will.

* A decimal.Decimal? That has its own issues (for instance, (x+y)/2
can return a value that isn't between x and y), and it still can't
represent two thirds properly.

* A fractions.Fraction? Well, at least that can perfectly represent a
ratio of integers. But it plays REALLY badly with other non-integer
types, and other operations than basic arithmetic (ever tried to take
the square root of a fraction?), so it's really only suited to
situations where you're working exclusively with fractions.

* Something else?

You say that Py3 making 1/10 => 0.1 was a "very retrograde change".
Why? Yes, now you're seeing floats; but would you be less surprised by
the Py2 version? Sure, Py2 has your little toy example working:

>>> (1/10) * (1/10) * 10 == (1/10)
True

but that's because 1/10 is zero, not because it's been represented
accurately! The biggest advantage of having integer division yield an
integer is that it forces people to think about their data types; but
since Python's float has a standing (core language support) that
Decimal and Fraction don't have, I don't think it's a problem. It's
the same with the complex type:

>>> 2 ** 0.5
1.4142135623730951
>>> (-2) ** 0.5
(8.659560562354934e-17+1.4142135623730951j)

Core data types will migrate between themselves as needed. (And this
proves some of the inherent inaccuracies in floating point arithmetic;
real number arithmetic says that the square root of -2 has a zero real
part.)

Interestingly, fractions.Fraction doesn't handle non-integer
exponentiation, and punts to float:

>>> fractions.Fraction(4) ** fractions.Fraction(2)
Fraction(16, 1)
>>> fractions.Fraction(4) ** fractions.Fraction(2, 3)
2.5198420997897464
>>> fractions.Fraction(4) ** fractions.Fraction(3, 2)
8.0
>>> fractions.Fraction(-4) ** fractions.Fraction(1, 2)
(1.2246467991473532e-16+2j)

Some data types just aren't well suited to certain operations.

ChrisA

Grant Edwards

unread,
May 20, 2016, 10:17:54 PM5/20/16
to
On 2016-05-20, Steven D'Aprano <st...@pearwood.info> wrote:
> On Sat, 21 May 2016 05:20 am, Christopher Reimer wrote:
>
>> According to "Effective Python: 59 Specific Ways to Write Better Python"
>> by Brett Slatkin, Item 12 recommends against using the else block after
>> for and while loops (see page 25): "Avoid using else blocks after loops
>> because their behavior isn't intuitive and can be confusing."
>
> By that logic, we ought to:
>
> - avoid using floats because their behaviour isn't intuitive and
> can be confusing;

Well, a lot of people probably should avoid floats. I've often said
that anybody who hasn't taken a numerical methods class shouldn't be
allowed to use floating point.

--
Grant




Christopher Reimer

unread,
May 20, 2016, 10:24:19 PM5/20/16
to
On 5/20/2016 3:43 PM, Steven D'Aprano wrote:

> But the idea that you should avoid a Python feature while programming in
> Python because Javascript doesn't have it, or Ruby, or C, is surely the
> height of muddleheaded thinking. You're not programming Javascript, Ruby or
> C, you're programming in Python. The whole point of picking one language
> over another is to get access to the tools and features that language
> offers. Otherwise you're just wasting your time.

For many years I have resisted specializing in a programming language,
as I can easily write any program in pseudo code and figure out the
syntax for a particular language. Now it does help that most languages
have derived from C and share a common feature set (i.e., string,
integer, float, if/else, while, for, etc.). From my perspective, tacking
on an else block to the end of a for or while loop looks like a bug or a
not very well thought out feature. If I was translating a Python program
with for/else or while/else statements into a different language, those
statements will have to be rewritten anyway.

Thank you,

Chris R.


Chris Angelico

unread,
May 20, 2016, 10:31:53 PM5/20/16
to
On Sat, May 21, 2016 at 11:23 AM, Christopher Reimer
<christoph...@icloud.com> wrote:
> On 5/20/2016 3:43 PM, Steven D'Aprano wrote:
>
>> But the idea that you should avoid a Python feature while programming in
>> Python because Javascript doesn't have it, or Ruby, or C, is surely the
>> height of muddleheaded thinking. You're not programming Javascript, Ruby
>> or
>> C, you're programming in Python. The whole point of picking one language
>> over another is to get access to the tools and features that language
>> offers. Otherwise you're just wasting your time.
>
>
> For many years I have resisted specializing in a programming language, as I
> can easily write any program in pseudo code and figure out the syntax for a
> particular language. Now it does help that most languages have derived from
> C and share a common feature set (i.e., string, integer, float, if/else,
> while, for, etc.). From my perspective, tacking on an else block to the end
> of a for or while loop looks like a bug or a not very well thought out
> feature. If I was translating a Python program with for/else or while/else
> statements into a different language, those statements will have to be
> rewritten anyway.

That's fine, as long as you (a) restrict your programming languages to
those derived from C, and (b) restrict your programming style to the
common subset of them all. Trouble is, that "common subset" is
actually pretty small. Strings behave very differently in C and high
level languages, and for loops are *very* different in different
languages. So you'd be throwing out a large amount of expressiveness,
plus you're completely unable to use languages built on some other
model (eg LISP, or DeScribe Macro Language, or APL).

ChrisA

Christopher Reimer

unread,
May 20, 2016, 11:47:44 PM5/20/16
to
On 5/20/2016 7:31 PM, Chris Angelico wrote:

> On Sat, May 21, 2016 at 11:23 AM, Christopher Reimer
> <christoph...@icloud.com> wrote:
>> On 5/20/2016 3:43 PM, Steven D'Aprano wrote:
>>
>>> But the idea that you should avoid a Python feature while programming in
>>> Python because Javascript doesn't have it, or Ruby, or C, is surely the
>>> height of muddleheaded thinking. You're not programming Javascript, Ruby
>>> or
>>> C, you're programming in Python. The whole point of picking one language
>>> over another is to get access to the tools and features that language
>>> offers. Otherwise you're just wasting your time.
>>
>> For many years I have resisted specializing in a programming language, as I
>> can easily write any program in pseudo code and figure out the syntax for a
>> particular language. Now it does help that most languages have derived from
>> C and share a common feature set (i.e., string, integer, float, if/else,
>> while, for, etc.). From my perspective, tacking on an else block to the end
>> of a for or while loop looks like a bug or a not very well thought out
>> feature. If I was translating a Python program with for/else or while/else
>> statements into a different language, those statements will have to be
>> rewritten anyway.
> That's fine, as long as you (a) restrict your programming languages to
> those derived from C, and (b) restrict your programming style to the
> common subset of them all. Trouble is, that "common subset" is
> actually pretty small. Strings behave very differently in C and high
> level languages, and for loops are *very* different in different
> languages. So you'd be throwing out a large amount of expressiveness,
> plus you're completely unable to use languages built on some other
> model (eg LISP, or DeScribe Macro Language, or APL).

I don't have a problem with (a) because the majority of the programming
languages I've been exposed to have derived from the C language. No
offense to the LISPers, but LISP is a historical curiosity that I might
blow the dust off and take a look at someday. I'll probably learn
assembly language before I ever look at LISP. :)

But I disagree with (b) on restricting myself to a common subset of ALL
the programming languages. Pseudo code allows me to describe a program
in very general details. Implementing a program in a programming
language requires getting into very specific details. Of course, there
are major and minor differences from language to language. If an oddball
feature gets the job done, I'll use that. Or maybe not. If I'm uncertain
about something, I'll keep going back and forth until I'm satisfied one
way or another.

The else block tacked on to for and while loops in Python seems very
oddball-ish to me. I've always strive to follow best practice whenever
possible. The one book I've read -- and so far, the only book on that
feature -- recommends not using it. Based on my previous experience, I
don't disagree with that author's opinion. If I have a compelling reason
to use it, I'll use it. Or I'll simplify it to use helper functions.

If I wanted to write portable code, I would have stayed with... Java. O_o

Thank you,

Chris R.

Steven D'Aprano

unread,
May 20, 2016, 11:50:36 PM5/20/16
to
On Sat, 21 May 2016 10:24 am, Jon Ribbens wrote:

> On 2016-05-20, Ethan Furman <et...@stoneleaf.us> wrote:

>> If you don't take the extra step of _break_ it is the usual case.
>
> Having an "for: else:" clause without a "break" would be so unusual
> that it's literally nonexistent, because it would always be a bug.
> So no, it isn't the usual case for "for: else:".


What do you mean? A for...else without a break is perfectly legal code, and
does *EXACTLY* what it is documented as doing:

- first the "for" block runs, looping as appropriate;
- THEN the "else" block runs, *once*.

How is this "always a bug"?

Would you classify the second line here:

print("Hello World!")
pass


as a bug? What exactly would your bug report be? "pass statement does
nothing, as expected. It should do nothing. Please fix."


>> But as others have said, this isn't going to change now, and I'm okay
>> with that. But, please, be a bit more understanding of those who don't
>> immediately grok the for/else and while/else loops.
>
> You're misunderstanding me. I'm not saying that the meaning of
> "for: else:" is 100% intuitively obvious.

I should hope not, because as it stands with the horribly misleading
keyword "else" it is counter-intuitive and confusing. Which is a crying
shame, because it is a useful feature that actually does make a lot of
sense, if only the keyword were better!


> I'm saying that it's
> *more* obvious than it would be if it used any of the other existing
> keywords. I suppose I'm also saying that there isn't any other
> obvious word that could be made into a keyword that would be better
> than "else" (even if we assumed that adding a new keyword was a
> cost-free exercise).

Well, that's a matter of opinion. And you know what they same about
opinions... there are always two, the foolish, pig-ignorant one, and mine.

:-)



--
Steven

Chris Angelico

unread,
May 21, 2016, 12:02:07 AM5/21/16
to
On Sat, May 21, 2016 at 1:50 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> On Sat, 21 May 2016 10:24 am, Jon Ribbens wrote:
>
>> On 2016-05-20, Ethan Furman <et...@stoneleaf.us> wrote:
>
>>> If you don't take the extra step of _break_ it is the usual case.
>>
>> Having an "for: else:" clause without a "break" would be so unusual
>> that it's literally nonexistent, because it would always be a bug.
>> So no, it isn't the usual case for "for: else:".
>
>
> What do you mean? A for...else without a break is perfectly legal code, and
> does *EXACTLY* what it is documented as doing:
>
> - first the "for" block runs, looping as appropriate;
> - THEN the "else" block runs, *once*.
>
> How is this "always a bug"?
>
> Would you classify the second line here:
>
> print("Hello World!")
> pass
>
>
> as a bug? What exactly would your bug report be? "pass statement does
> nothing, as expected. It should do nothing. Please fix."
>

Yes, I would. It's not a bug in Python or CPython - it's a bug in the
second line of code there. It implies something that isn't the case.
It's like having this code:

if True:
pass
elif False:
pass
else:
assert True

It's well-defined code. You know exactly what Python should do. But is
it good code, or is it the sort of thing that gets posted here:

http://thedailywtf.com/articles/a-spiritual-journey

ChrisA

Rustom Mody

unread,
May 21, 2016, 1:18:14 AM5/21/16
to
On Saturday, May 21, 2016 at 1:51:19 AM UTC+5:30, Christopher Reimer wrote:
> On 5/20/2016 8:59 AM, Zachary Ware wrote:
>
Firstly: let me say that for the specific case of loop-else I am in violent
agreement: I find it so confusing that I dont even know what/how it works

Coming to the more general attitude expressed above, this view can be
eminently sensible or dangerously retrogressive depending...

It is a sound and sane view because the field of computer science exists
and legitimately so.
Which means that, even though when we look around at the field of programming
languages what we are struck by is a bedlam of
- advertising
- fanboyism
- latest and bestest koolaid
- holy (cow) wars

And a corresponding deficit of anything really conceptual, real advances, real
understanding; in short truckloads of BS.

OTOH the fact that the field of CS exists and is not (only) BS is a good thing.

IOW sticking to the well-established canonical core is sound policy compared to
jumping onto the latest loud-rattling bandwagon.

And yet...
Human beings have the propensity of sticking to the norm rather than deviating
even when the norm is grievously in error.
The following lists some amazingly long lasting errors:
http://blog.languager.org/2016/01/how-long.html

This is related to the pedagogic principle called "Law of Primacy":

| Things learned first create a strong impression in the mind that is difficult
| to erase. For the instructor, this means that what is taught must be right
| the first time.

Given that for the most part, most of us are horribly uneducated [
http://www.creativitypost.com/education/9_elephants_in_the_classroom_that_should_unsettle_us
]
how do we go about correcting our wrong primacies?

Given that you imagine Lisp is a historical curiosity (have you heard of clojure?)
And java (and presumably OOP) is relevant
[see Stepanov on OOP: https://en.wikipedia.org/wiki/Object-oriented_programming#Criticism
]
I suggest you start with:
http://blog.languager.org/2016/01/primacy.html

Marko Rauhamaa

unread,
May 21, 2016, 2:03:41 AM5/21/16
to
the...@gmail.com:

> You seem to have missed the point. Nobody is suggesting, I don't
> believe, that all of a language should be intuitive. Rather that if
> any part of it is unnecessarily counter-intuitive, it may be worth
> looking for a better solution. Python is a very well designed language
> when it comes to in linguistic presentation. In this case however, it
> is not only unintuitive but counter-intuitive.

The for-else construct is a highly practical feature. It is in no way
inherently counter-intuitive. As any new thing, it needs to be learned
and used to be appreciated.


Marko

Erik

unread,
May 21, 2016, 3:20:23 AM5/21/16
to
On 20/05/16 01:06, Steven D'Aprano wrote:

> In my experience, some people (including me) misunderstand "for...else" to
> mean that the else block runs if the for block *doesn't*. It took me the
> longest time to understand why this didn't work as I expected:
>
> for x in seq:
> pass
> else:
> print("seq is empty")

So why don't we consider if that should be a syntax error - "else"
clause on "for" loop with no "break" in its body? I know that doesn't
change your fundamental mental model, but it's a hint that it's wrong :)

As it's backwards-incompatible, it could be introduced using a
__future__ import (a precedent is 'generators' and the "yield" keyword
back in the day) which those who would like the new check could add to
the top of their sources.

But also, as you say, any instances of that construct in the wild is
almost certainly a bug, so it would be good to be able to test code
using a command-line or similar switch to turn on the behaviour by
default for testing existing bodies of code.

I notice that pylint complains about this (as a warning). Is there any
reason why this should _not_ just be considered an error and be done
with it?

E.

Chris Angelico

unread,
May 21, 2016, 3:29:34 AM5/21/16
to
On Sat, May 21, 2016 at 5:20 PM, Erik <pyt...@lucidity.plus.com> wrote:
> On 20/05/16 01:06, Steven D'Aprano wrote:
>
>> In my experience, some people (including me) misunderstand "for...else" to
>> mean that the else block runs if the for block *doesn't*. It took me the
>> longest time to understand why this didn't work as I expected:
>>
>> for x in seq:
>> pass
>> else:
>> print("seq is empty")
>
>
> So why don't we consider if that should be a syntax error - "else" clause on
> "for" loop with no "break" in its body? I know that doesn't change your
> fundamental mental model, but it's a hint that it's wrong :)
>
> As it's backwards-incompatible, it could be introduced using a __future__
> import (a precedent is 'generators' and the "yield" keyword back in the day)
> which those who would like the new check could add to the top of their
> sources.
>
> But also, as you say, any instances of that construct in the wild is almost
> certainly a bug, so it would be good to be able to test code using a
> command-line or similar switch to turn on the behaviour by default for
> testing existing bodies of code.
>
> I notice that pylint complains about this (as a warning). Is there any
> reason why this should _not_ just be considered an error and be done with
> it?

Because it's not the language parser's job to decide what's "sensible"
and what's "not sensible". That's a linter's job. Some things
fundamentally can't work, but others work fine and might just be
not-very-useful - for example:

def f(x):
x if x <= 2 else x * f(x-1)

This is syntactically-legal Python code, but it probably isn't doing
what you want it to (in Python, a bare final expression does NOT
become the function's return value, although that does happen in other
languages). A linter should look at this and give a warning (even if
the else part has side effects, the first part can't, and the
condition shouldn't), but the language will happily evaluate it (this
example from CPython 3.6):

>>> dis.dis(f)
2 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (2)
6 COMPARE_OP 1 (<=)
9 POP_JUMP_IF_FALSE 18
12 LOAD_FAST 0 (x)
15 JUMP_FORWARD 17 (to 35)
>> 18 LOAD_FAST 0 (x)
21 LOAD_GLOBAL 0 (f)
24 LOAD_FAST 0 (x)
27 LOAD_CONST 2 (1)
30 BINARY_SUBTRACT
31 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
34 BINARY_MULTIPLY
>> 35 POP_TOP
36 LOAD_CONST 0 (None)
39 RETURN_VALUE

So, yes, this function might load up the value of x (position 12),
then jump to 35 and discard that value, before unconditionally
returning None. Not a problem. It's up to the linter, and ONLY the
linter, to tell you about this.

ChrisA

Marko Rauhamaa

unread,
May 21, 2016, 4:37:57 AM5/21/16
to
Erik <pyt...@lucidity.plus.com>:

> On 20/05/16 01:06, Steven D'Aprano wrote:
>> In my experience, some people (including me) misunderstand
>> "for...else" to mean that the else block runs if the for block
>> *doesn't*. It took me the longest time to understand why this didn't
>> work as I expected:
>>
>> for x in seq:
>> pass
>> else:
>> print("seq is empty")
>
> So why don't we consider if that should be a syntax error - "else"
> clause on "for" loop with no "break" in its body? I know that doesn't
> change your fundamental mental model, but it's a hint that it's wrong
> :)

While we are at it, we should trigger a syntax error in these cases:

a = 0
b += a # pointless to add a 0

c = 1
d *= c # pointless to multiply by 1

if False: # pointless to test for truth in falsity
...

However, all of these "pointless" constructs crop up in real situations,
and artifically flagging them as errors would lead to unnecessary
frustration.

For example,

if False:
yield None

is the way to create a generator that doesn't generate any output.


Marko

Steven D'Aprano

unread,
May 21, 2016, 5:56:49 AM5/21/16
to
On Sat, 21 May 2016 02:01 pm, Chris Angelico wrote:

> On Sat, May 21, 2016 at 1:50 PM, Steven D'Aprano <st...@pearwood.info>
> wrote:

>> Would you classify the second line here:
>>
>> print("Hello World!")
>> pass
>>
>>
>> as a bug? What exactly would your bug report be? "pass statement does
>> nothing, as expected. It should do nothing. Please fix."
>>
>
> Yes, I would. It's not a bug in Python or CPython - it's a bug in the
> second line of code there. It implies something that isn't the case.

What do you think it implies?

What part of the docs for "pass" implies this thing?

help("pass"):

``pass`` is a null operation --- when it is executed, nothing
happens. It is useful as a placeholder when a statement is
required syntactically, but no code needs to be executed, for
example: [examples snipped]



> It's like having this code:
>
> if True:
> pass
> elif False:
> pass
> else:
> assert True

Hmmm. Well, let see:


py> if True:
... pass
... elif False:
... pass
... else:
... assert True
...
Traceback (most recent call last):
File "<stdin>", line 6, in <module>
AssertionError


What witchcraft is this?

S
P
O
I
L
E
R

S
P
A
C
E

I'm running Python 2, and have redefined True = False.

But even in Python 3, what exactly is the bug supposed to be? The Python 3
compiler, as naive and simple as it is, is perfectly capable of compiling
out the dead code:

py> dis.dis("""if True: pass
... elif False: pass
... else: assert True
... """)
1 0 LOAD_CONST 0 (None)
3 RETURN_VALUE


So the worst we can say about this is that it is pointless dead code.


> It's well-defined code. You know exactly what Python should do. But is
> it good code,

Well, it's not *great* code, that's for sure. Is it good code? That depends.
There are certainly semantic differences between Python 2 and 3, and
without knowing the intention of the author (that would be you) its hard to
say exactly what the code should be. But it's *legal* code that does
exactly what the language documentation says it ought to do.


> or is it the sort of thing that gets posted here:
>
> http://thedailywtf.com/articles/a-spiritual-journey

I'm not sure how that specific Daily-WTF article is relevant.

But in general, is it worthy of Daily-WTF? No, I don't think so. I think
that your "True, False" example could do with some refactoring and cleanup:
perhaps it is dead code that should be completely removed, but that's not
necessarily Daily-WTF worthy. Or it's some part of some sort of Python 2+3
compatibility layer intending to detect rebindings to True or False, in
which case it is certainly ugly (and probably buggy) but may be needed.

As far as my example, using "pass" in the code... no, it's not WTF-worthy
either. It's at worst worth a raised eyebrow. Without knowing the context
of where it came from, it isn't even clear that it is pointless code.
Perhaps it is from a test suite checking that `pass` is correctly parsed,
compiled and executed as a do-nothing statement.



--
Steven

Steven D'Aprano

unread,
May 21, 2016, 6:05:32 AM5/21/16
to
On Sat, 21 May 2016 03:18 pm, Rustom Mody wrote:

> Given that for the most part, most of us are horribly uneducated [
>
http://www.creativitypost.com/education/9_elephants_in_the_classroom_that_should_unsettle_us
> ]
> how do we go about correcting our wrong primacies?


An interesting article, but very US-centric.

Nevertheless, let's not forget that the general school system is designed to
churn out more-or-less identical workers capable of pulling the levers on
late 19th and early 20th century machines for their bosses. The fact that
it does more than that, even if badly, is a bonus.


--
Steven

Chris Angelico

unread,
May 21, 2016, 6:08:37 AM5/21/16
to
On Sat, May 21, 2016 at 7:56 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> On Sat, 21 May 2016 02:01 pm, Chris Angelico wrote:
>
>> On Sat, May 21, 2016 at 1:50 PM, Steven D'Aprano <st...@pearwood.info>
>> wrote:
>
>>> Would you classify the second line here:
>>>
>>> print("Hello World!")
>>> pass
>>>
>>>
>>> as a bug? What exactly would your bug report be? "pass statement does
>>> nothing, as expected. It should do nothing. Please fix."
>>>
>>
>> Yes, I would. It's not a bug in Python or CPython - it's a bug in the
>> second line of code there. It implies something that isn't the case.
>
> What do you think it implies?
>
> What part of the docs for "pass" implies this thing?
>
> help("pass"):
>
> ``pass`` is a null operation --- when it is executed, nothing
> happens. It is useful as a placeholder when a statement is
> required syntactically, but no code needs to be executed, for
> example: [examples snipped]

So why is a statement required syntactically after that print call?
Surely that implies something about the programmer's intent? It
certainly isn't required according to the code you've shown me; and if
someone submitted this code to me, I'd query it ("was there something
else meant to be here?").

ChrisA

Steven D'Aprano

unread,
May 21, 2016, 6:39:38 AM5/21/16
to
On Sat, 21 May 2016 05:20 pm, Erik wrote:

> On 20/05/16 01:06, Steven D'Aprano wrote:
>
>> In my experience, some people (including me) misunderstand "for...else"
>> to mean that the else block runs if the for block *doesn't*. It took me
>> the longest time to understand why this didn't work as I expected:
>>
>> for x in seq:
>> pass
>> else:
>> print("seq is empty")
>
> So why don't we consider if that should be a syntax error - "else"
> clause on "for" loop with no "break" in its body? I know that doesn't
> change your fundamental mental model, but it's a hint that it's wrong :)

Just for the record, that's not my mental model *now*.

It took me a long time to work out what for...else was actually doing, but
some years ago I finally managed to do so. It's my argument that had the
keyword been "then" (implying that the `then` block is unconditionally
executed after the `for` block, which is the actual behaviour of the
interpreter) rather than "else" implying that it is an alternative to the
`for` block) I wouldn't have come up with the wrong mental model in the
first place. And I'm not the only one who has come up with the same, wrong,
model.

But you do ask a good question. Why isn't for...else with no break a syntax
error? I suppose it could be. But that's a *stylistic* question, and Python
generally treats that as "none of the compiler's business". It's not the
business of the compiler to enforce good code, only legal code. We can
legally write lots of code of questionable value:

while 0:
print("surprise!")

n = 1 + int(x) - 1

x = x

try:
something()
finally:
pass


without the compiler judging us and making it a syntax error. Why should
for...else be any different? The behaviour is perfectly well defined:

- first the `for` block runs, as many times as needed;
- then the `else` block runs, once.


Of course, the usual four keywords (continue, break, return, raise) will
change the order of execution by jumping to:

- the top of the for loop;
- past the for...else blocks;
- out of the current function;
- the nearest exception handler

respectively. But in the absence of a jump, for...else works as specified,
regardless of whether there is a break in it or not.


> As it's backwards-incompatible, it could be introduced using a
> __future__ import (a precedent is 'generators' and the "yield" keyword
> back in the day) which those who would like the new check could add to
> the top of their sources.

Sure, we *could* do this, but as I said, it does go against the usual
philosophy that the compiler shouldn't make judgements on what is good code
and what isn't.


> But also, as you say, any instances of that construct in the wild is
> almost certainly a bug,

I don't think it was me calling it a bug.


> so it would be good to be able to test code
> using a command-line or similar switch to turn on the behaviour by
> default for testing existing bodies of code.

I think that switch is called "use Pylint, PyChecker, Jedi or some other
opinionated linter or style-checker".


> I notice that pylint complains about this (as a warning). Is there any
> reason why this should _not_ just be considered an error and be done
> with it?

Because there's nothing actually broken with it.

There's lots of code which a human programmer would recognise as "silly",
or "wrong", but is well-defined and legal. Just because you can't think of
a reason to do something doesn't mean it should be prohibited.



--
Steven

Steven D'Aprano

unread,
May 21, 2016, 6:55:32 AM5/21/16
to
On Sat, 21 May 2016 08:08 pm, Chris Angelico wrote:

> On Sat, May 21, 2016 at 7:56 PM, Steven D'Aprano <st...@pearwood.info>
> wrote:
>> On Sat, 21 May 2016 02:01 pm, Chris Angelico wrote:
>>
>>> On Sat, May 21, 2016 at 1:50 PM, Steven D'Aprano <st...@pearwood.info>
>>> wrote:
>>
>>>> Would you classify the second line here:
>>>>
>>>> print("Hello World!")
>>>> pass
>>>>
>>>>
>>>> as a bug? What exactly would your bug report be? "pass statement does
>>>> nothing, as expected. It should do nothing. Please fix."
>>>>
>>>
>>> Yes, I would. It's not a bug in Python or CPython - it's a bug in the
>>> second line of code there. It implies something that isn't the case.
>>
>> What do you think it implies?
>>
>> What part of the docs for "pass" implies this thing?
>>
>> help("pass"):
>>
>> ``pass`` is a null operation --- when it is executed, nothing
>> happens. It is useful as a placeholder when a statement is
>> required syntactically, but no code needs to be executed, for
>> example: [examples snipped]
>
> So why is a statement required syntactically after that print call?

Who said it was required?

The docs say that `pass` is useful when a statement is required, not the
other way: you can put `pass` anywhere a statement can go, without it being
required.


> Surely that implies something about the programmer's intent? It
> certainly isn't required according to the code you've shown me; and if
> someone submitted this code to me, I'd query it ("was there something
> else meant to be here?").

Sure. Does that make it a bug? I don't think so. I think that a linter
should flag it as unnecessary code, but the compiler certainly shouldn't
prohibit it. And a human reviewer might be able to judge whether it belongs
or not.

Some people might not like "FIXME" or "XXX" comments to flag missing code,
and use `pass` statements as placeholders. Who are we to tell them they
shouldn't?

I think we agree that it's not the compiler's job to enforce good coding
standards.




--
Steven

Chris Angelico

unread,
May 21, 2016, 7:10:48 AM5/21/16
to
On Sat, May 21, 2016 at 8:55 PM, Steven D'Aprano <st...@pearwood.info> wrote:
> I think we agree that it's not the compiler's job to enforce good coding
> standards.

Yep. I believe we are in (possibly belligerent) agreement.

ChrisA

Steven D'Aprano

unread,
May 21, 2016, 7:26:39 AM5/21/16
to
Yes, this!

I might have issues with the keyword chosen, but the feature itself is very
clever.

Does anyone know of other languages that include the same feature? It's very
rare for Python to innovate in language features.

(I wonder if it came from ABC?)





--
Steven

Steven D'Aprano

unread,
May 21, 2016, 7:27:00 AM5/21/16
to
On Sat, 21 May 2016 09:57 am, Dennis Lee Bieber wrote:

> On Fri, 20 May 2016 11:55:34 -0000 (UTC), Jon Ribbens
> <jon+u...@unequivocal.co.uk> declaimed the following:
>
>>
>>I would find that very confusing. "then:" makes it sound like
>>executing that block is the usual case, when in practice it is
>>usually the exception - the fallback code if the expected value
>>was not found.
>
> And I'll second that...
>
> In those languages that use "then" as a keyword, it separates the "if"
> conditional from the "true" block of code. Or -- visualize replacing the
> ":" on the "for" with the word "then"
>
> for x in sequence then
> do stuff with x
> else
> do something with no x
>
>
> If a different keyword is to be introduced, I nominate "otherwise"

"otherwise" fails for the same reason that "else" fails: it suggests that
the else block is an alternative to the for block, which is exactly what it
is NOT.




--
Steven

Ian Kelly

unread,
May 21, 2016, 9:52:30 AM5/21/16
to
On Sat, May 21, 2016 at 5:26 AM, Steven D'Aprano <st...@pearwood.info> wrote:
> Does anyone know of other languages that include the same feature? It's very
> rare for Python to innovate in language features.
>
> (I wonder if it came from ABC?)

According to Raymond Hettinger starting at about 15:50 in this video:

http://pyvideo.org/video/1780/transforming-code-into-beautiful-idiomatic-pytho

It was invented by Donald Knuth (including the choice of keyword).

Grant Edwards

unread,
May 21, 2016, 11:20:51 AM5/21/16
to
On 2016-05-21, Ian Kelly <ian.g...@gmail.com> wrote:
> On Sat, May 21, 2016 at 5:26 AM, Steven D'Aprano <st...@pearwood.info> wrote:
>
>> Does anyone know of other languages that include the same feature?
>> It's very rare for Python to innovate in language features.
>>
>> (I wonder if it came from ABC?)
>
> According to Raymond Hettinger starting at about 15:50 in this
> video:
>
> http://pyvideo.org/video/1780/transforming-code-into-beautiful-idiomatic-pytho
>
> It was invented by Donald Knuth (including the choice of keyword).

If true, that alone dismisses with prejudice any question of changing it.

As if backwards compatibility weren't a compelling enough argument.

Besides which, I happen to think it makes sense the way it is.

[I haven't been paying very close attention to this thread for a
while, so I assume that "this feature" and "it" still refer to having
an else clause in for/while loops?]

I can't count the number of times I've wished C had such a feature,
and have sometimes resorted to using a "goto" to get the same
semantics in an easy to read/understand way.

--
Grant

Christopher Reimer

unread,
May 21, 2016, 12:53:11 PM5/21/16
to
Under various proposals in the U.S., everyone will soon learn how to
program and/or become a computer scientist. Won't be long before some
snotty-nosed brat graduates from preschool, takes a look at your code,
and poops in his diapers. He will then dips his finger into his diaper,
write on the whiteboard how your code can be written in a single line,
and summary dismiss you with security escorting you off the premises.

Gotta love the future. :)

Thank you,

Chris R.

Marko Rauhamaa

unread,
May 21, 2016, 1:08:45 PM5/21/16
to
Christopher Reimer <christoph...@icloud.com>:

> Under various proposals in the U.S., everyone will soon learn how to
> program and/or become a computer scientist. Won't be long before some
> snotty-nosed brat graduates from preschool, takes a look at your code,
> and poops in his diapers. He will then dips his finger into his
> diaper, write on the whiteboard how your code can be written in a
> single line, and summary dismiss you with security escorting you off
> the premises.
>
> Gotta love the future. :)

Unfortunately, most CS graduates don't seem to know how to program.

Yes, some highschoolers could excel in the post of a senior software
engineer -- I've had the privilege of working alongside several
specimens. However, it has been known for half a century that good
developers are hard to come by.

I think it is essential to learn the principles of programming just like
it is essential to learn the overall principles of nuclear fission or be
able to locate China on the map. However, a small minority of humanity
will ever earn a living writing code.

At the same time, it may be that in the not-too-distant future, the
*only* jobs available will be coding jobs as we start to take the
finishing steps of automating all manufacturing, transportation and
services. Then, we will have a smallish class of overworked coders who
have no use or time for money and vast masses of jobless party-goers who
enjoy the fruits of the coders' labor.


Marko

Erik

unread,
May 21, 2016, 4:51:43 PM5/21/16
to
On 21/05/16 11:39, Steven D'Aprano wrote:
> Just for the record, that's not my mental model *now*.

Sure. And I should have written "one's mental model" - the model of
anyone writing that code (not you personally) who thought the same at
the time.

> It took me a long time to work out what for...else was actually doing, but
> some years ago I finally managed to do so.

Coming from a partly assembler background, I happened to get it straight
away just because the thought of separate jump targets for the loop
condition being satisfied and breaking out of the loop being different
was quite natural (and a very nice feature to have in a high level
language!).

> But you do ask a good question. Why isn't for...else with no break a syntax
> error? I suppose it could be. But that's a *stylistic* question, and Python
> generally treats that as "none of the compiler's business". It's not the
> business of the compiler to enforce good code, only legal code.

On 21/05/16 08:29, Chris Angelico wrote:
> It's up to the linter, and ONLY the linter, to tell you about this.

Apologies for joining two threads here, but my reply is the same to
both, so it makes sense to me.


Let me tell you a story ;) <wibbly-wobbly-lines> Back in the mid-to-late
1980s I worked with C compilers on hardware that could take several
minutes to compile even a fairly trivial program. They errored on
syntactically incorrect code and happily compiled syntactically correct
code. Sometimes the output of the compiler wouldn't execute as expected
because of "undefined behaviour" of some parts of the language (which
the compilers could quite legally accept but would not produce valid
code for - even illegal ops are fair game at that point). They would
create valid code for the valid syntax of buggy code ("if (x = y) {
foo(); }") without a whimper.

At that time, we had a program called 'lint'. Every so often we might
run it on our sources and find all sorts of questionable behaviour that
our code might have that we should look harder at (such as that above).
We didn't run it all the time because it took so much longer to run than
the compiler itself.

Over time, some compilers started adding the checks and advisories that
"lint" gave to their messages. For some branded compilers ("Green
Hills", "SAS/C"), this even became a selling point. They started to do
this while still retaining the original compilation speed.

Things got faster, more was added to the compiler, and once the compiler
started to do everything it did and more, lint died(*).

</wibbly-wobbly-lines>

And now, today, the compilers all do far more than the original 'lint'
program did in almost zero time every time some source is compiled. It
is free; it is not something one has to remember to run every so often.




So, back to Python ;)

The responses of "we can all write suspect/bad/ineffectual code - so
just run the linter" takes me back those 30 years to when we HAD to do
that with our C code too ...

There must be a better way.

I realise that Python has the issue that sometimes the person doing the
compilation is the end user (by virtue of them executing a .py file), so
lint-style compiler warnings aren't really appropriate - and that's the
reason why I suggested a syntax error: I understand that it's not really
any such thing, but it's all I had to work with and it ensured such code
could not get as far as the end user.


So I guess my question is perhaps whether Python compilers should start
to go down the same path that C compilers did 30 years ago (by starting
to include some linter functionality) but in a way that only outputs the
messages to developers and not end users. Also, the current "pylint"
blurs the edges between style (identifier names) and questionable code
("for/else" with no "break").



E. <-- waiting to be shot down again.



(*) Though to be fair, there are now even more deeply checking
(commercial) static tools available, which effectively fill the gap that
'lint' used to.

Michael Selik

unread,
May 21, 2016, 10:56:12 PM5/21/16
to
On Sat, May 21, 2016, 4:52 PM Erik <pyt...@lucidity.plus.com> wrote:

> So I guess my question is perhaps whether Python compilers should start
> to go down the same path that C compilers did 30 years ago (by starting
> to include some linter functionality)
>

Well, there's that whole optional type hints thing. You should be asking
how to preserve Python's simplicity while adding more static analysis
features.

>

Steven D'Aprano

unread,
May 21, 2016, 10:57:27 PM5/21/16
to
On Sun, 22 May 2016 06:48 am, Erik wrote:

> Let me tell you a story ;) <wibbly-wobbly-lines> Back in the mid-to-late
> 1980s I worked with C compilers on hardware that could take several
> minutes to compile even a fairly trivial program. They errored on
> syntactically incorrect code and happily compiled syntactically correct
> code. Sometimes the output of the compiler wouldn't execute as expected
> because of "undefined behaviour" of some parts of the language (which
> the compilers could quite legally accept but would not produce valid
> code for - even illegal ops are fair game at that point). They would
> create valid code for the valid syntax of buggy code ("if (x = y) {
> foo(); }") without a whimper.

Don't get me started about C and undefined behaviour.

Fortunately, Python has nothing even remotely like C undefined behaviour.


> At that time, we had a program called 'lint'.
[...]
> And now, today, the compilers all do far more than the original 'lint'
> program did in almost zero time every time some source is compiled. It
> is free; it is not something one has to remember to run every so often.

This is certainly not the case for Python. With C, you run your
compiler+linter once, and it builds an executable which can then run
without the compiler or linter.

With Python, *every time you run* the code, the compiler runs. The compiler
is the interpreter. It can, sometimes, skip some of the compilation steps
(parsing of source code) by use of cached byte-code files, but not all of
them. Including a linter will increase the size of the compiler
significantly, which much be distributed or installed for even the smallest
Python script, and it will have runtime implications re compilation time,
execution time, and memory use.

If you make the linter optional, say, a Python module that you install
separately and run only if you choose, then you have the status quo.


> So, back to Python ;)
>
> The responses of "we can all write suspect/bad/ineffectual code - so
> just run the linter" takes me back those 30 years to when we HAD to do
> that with our C code too ...
>
> There must be a better way.

Yes. And the better way is... don't write a language where you NEED a linter
because the language specification is so fecking *insane* that no human
being can reliably write correct code without running into undefined
behaviour which *can and will* have effects that propagate in both
directions, contaminating code which is correct in unpredictible ways.

https://blogs.msdn.microsoft.com/oldnewthing/20140627-00/?p=633/

One difference between C and Python is that most of the things which Python
linters look at don't actually have implications for correctness. Here are
a few of the features that Pylint looks at:

* line length;
* variable naming standards;
* unused imports;
* unnecessary semi-colons;
* use of deprecated modules;

and a complete list here:

http://pylint-messages.wikidot.com/all-codes

As you can see, most of the actual errors Pylint will pick up would result
in a runtime error, e,g, opening a file with an invalid mode. Most codes
are for code quality issues, related to maintenance issues, not bug
detection.

Coming back to for...else with no break, the behaviour is perfectly
well-defined, and does exactly what it is documented as doing. It's hard to
see that it is a "bug" for something to do what exactly what it is designed
to do. If somebody, for their own idiosyncratic reasons, wants to write:

for x in seq:
spam()
else:
eggs()

(Note: I've done this -- see below.)

and the language insists on a break, they will just pointlessly defeat the
compiler:

shutup_stupid_compiler = False
for x in seq:
if shutup_stupid_compiler: break
spam
else:
eggs


Thus entering an arms race where the compiler is seen as something to be
silenced rather than something that helps you.

I said that I've written for...else with no break. Why would I do such a
thing? Because I wanted to clearly mark that the code in the else was a
unit of code that went with the for-loop.

Code displays varying levels of cohesiveness. Python lets you group related
code in four levels:

- the function/method;
- the class;
- the module;
- the package


but sometimes you have code which is smaller than a function that needs to
be considered as a unit, but is not enough to justify putting it into a
function. When that happens, we usually delimit it with comments, or
sometimes even just a blank line:

code
that
goes
together

different
bunch
of
code
that
goes
together


So, when I had a bunch of code that included a for-loop, and something
immediately after the for-loop, I could have written:


for x in seq:
block
code that goes
with the loop

different bunch
of code


but I thought it communicated the grouping better to write it as:

for x in seq:
block
else: # runs after the for
code that goes
with the loop
different bunch
of code


I've since changed my mind. That's too subtle and too idiosyncratic for my
liking. It clashes with the keyword "else", which I maintain is badly
named. If it was named "next", which I maintain describes what it does much
better, then things might be different, but given the status quo, I've gone
back to doing it the old-fashioned way, with a comment.

But the point is, that's a matter of *taste*, not a matter for the compiler.
If somebody else wanted to do it my way, well, that's between them and
whoever else works on their code.

You probably wouldn't want the compiler to raise a syntax error because you
put a blank line or a comment somewhere the compiler writer disapproved
off. For example, some people insist that the first line of code must
follow immediately after the docstring, some prefer to leave a blank line:

def spam():
"""Docs"""
code

def eggs():
"""Docs"""

code


Your suggestion to raise a syntax error in the case of for...else without
break strikes me as no different from the idea that we should raise a
syntax error if there is/isn't a blank line after the docstring. (Choose
one.)



--
Steven

Jon Ribbens

unread,
May 22, 2016, 10:19:25 AM5/22/16
to
On 2016-05-21, Chris Angelico <ros...@gmail.com> wrote:
> On Sat, May 21, 2016 at 10:35 AM, Jon Ribbens
><jon+u...@unequivocal.co.uk> wrote:
>> To be fair, I'm very sympathetic to that argument. I think programming
>> languages should never magically produce floats out of nowhere unless
>> the programmer has explicitly done "import float" or "float('3.23')"
>> or somesuch. They're misunderstood so often that any convenience
>> they provide is outweighed by the danger they bring.
>>
>> "(1/10) * (1/10) * 10 != (1/10)" anyone? I was distinctly unhappy with
>> the Python 3 "2/3 ~= 0.6666" thing and regard it as a very retrograde
>> change.
>
> The trouble is, what SHOULD 2/3 return?
>
> * An integer? Makes a lot of sense to a C programmer. Not so much to
> someone who is expecting a nonzero value. This isn't terrible (hey,
> Python 2 managed with it no problem), but will definitely confuse a
> number of people.

Yes, it should return an integer - and not because I think Python
should behave like C on principle, but because:

Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.

and floats are complicated.

> * A float? That's what we currently have. Not perfect, but it's going
> to confuse less people than 0 will.

That's a trap for those people though - it lulls them into thinking
that they understand what's going on, when in fact they don't,
because they don't understand floats, because almost nobody
understands floats. So they don't understand their program, and
- even worse - they don't know that they don't understand it.

Programming languages should do what they are told, and very little
more. They should not wander off on surprising jaunts of their own
invention out of the control of the programmer. It should be possible
to know and understand the language, or at least the subset of it
that you are likely to need for your everyday purposes. Floats are
generally not understood, so they shouldn't be suddenly turning up
un-called for.

Python generally sticks to this idea very well, which is one of the
things that I think make it an excellent programming language, so it
is a shame that in the Python 2 to Python 3 change when mistakes were
being rectified, a new one was introduced.

Marko Rauhamaa

unread,
May 22, 2016, 10:58:39 AM5/22/16
to
Jon Ribbens <jon+u...@unequivocal.co.uk>:

> On 2016-05-21, Chris Angelico <ros...@gmail.com> wrote:
>> The trouble is, what SHOULD 2/3 return?
>
> [...]
>
> Yes, it should return an integer - and not because I think Python
> should behave like C on principle, but because:
>
> Explicit is better than implicit.
> Simple is better than complex.
> Complex is better than complicated.
>
> and floats are complicated.

Scheme has the best of both worlds:

scheme@(guile-user)> 2/3
$1 = 2/3
scheme@(guile-user)> (exact->inexact $1)
$2 = 0.6666666666666666

> That's a trap for those people though - it lulls them into thinking
> that they understand what's going on, when in fact they don't, because
> they don't understand floats, because almost nobody understands
> floats. So they don't understand their program, and - even worse -
> they don't know that they don't understand it.

I don't understand this rant. Numeric programming is one of the oldest
uses for computers. Rounding errors have been there since the beginning.
If you think people have a hard time getting floats, integers are at
least as hard to get. How about classes, closures, threads, asyncio...?

Python ought to be the perfect language for seasoned experts. It doesn't
need to be dumbed down for noobs.


Marko

Jon Ribbens

unread,
May 22, 2016, 11:13:21 AM5/22/16
to
On 2016-05-22, Marko Rauhamaa <ma...@pacujo.net> wrote:
> Jon Ribbens <jon+u...@unequivocal.co.uk>:
>> That's a trap for those people though - it lulls them into thinking
>> that they understand what's going on, when in fact they don't, because
>> they don't understand floats, because almost nobody understands
>> floats. So they don't understand their program, and - even worse -
>> they don't know that they don't understand it.
>
> I don't understand this rant.

And I don't understand your use of the word "rant".

> Numeric programming is one of the oldest uses for computers.
> Rounding errors have been there since the beginning. If you think
> people have a hard time getting floats, integers are at least as
> hard to get.

That is clearly nonsense.

> How about classes, closures, threads, asyncio...?

People don't tend to think of those as simple things that they already
understand when they don't. Also, those things don't tend to turn up
to the party uninvited.

Steven D'Aprano

unread,
May 22, 2016, 11:19:18 AM5/22/16
to
On Mon, 23 May 2016 12:15 am, Jon Ribbens wrote:

> On 2016-05-21, Chris Angelico <ros...@gmail.com> wrote:
>> On Sat, May 21, 2016 at 10:35 AM, Jon Ribbens
>><jon+u...@unequivocal.co.uk> wrote:
>>> To be fair, I'm very sympathetic to that argument. I think programming
>>> languages should never magically produce floats out of nowhere unless
>>> the programmer has explicitly done "import float" or "float('3.23')"
>>> or somesuch. They're misunderstood so often that any convenience
>>> they provide is outweighed by the danger they bring.
>>>
>>> "(1/10) * (1/10) * 10 != (1/10)" anyone? I was distinctly unhappy with
>>> the Python 3 "2/3 ~= 0.6666" thing and regard it as a very retrograde
>>> change.
>>
>> The trouble is, what SHOULD 2/3 return?
>>
>> * An integer? Makes a lot of sense to a C programmer. Not so much to
>> someone who is expecting a nonzero value. This isn't terrible (hey,
>> Python 2 managed with it no problem), but will definitely confuse a
>> number of people.
>
> Yes, it should return an integer - and not because I think Python
> should behave like C on principle, but because:
>
> Explicit is better than implicit.
> Simple is better than complex.
> Complex is better than complicated.
>
> and floats are complicated.

How is this any better though? Complicated or not, people want to divide 1
by 2 and get 0.5. That is the functional requirement. Furthermore, they
want to use the ordinary division symbol / rather than having to import
some library or call a function.

Having 1/2 return 0 (as Python 2 does by default) doesn't make the language
any less complicated. It doesn't avoid the complexity of floats, it merely
breaks the principle of least surprise, and forces the programmer to add
what they consider to be an unnecessary ".0" to one or the other of the
operands.

Swapping to a base-10 float will be numerically even worse than the binary
floats we use now. Swapping to rationals add complexity and performance
issues. So whatever you do, there is complexity and annoyance.


>> * A float? That's what we currently have. Not perfect, but it's going
>> to confuse less people than 0 will.
>
> That's a trap for those people though - it lulls them into thinking
> that they understand what's going on, when in fact they don't,
> because they don't understand floats, because almost nobody
> understands floats. So they don't understand their program, and
> - even worse - they don't know that they don't understand it.

And how does forcing them to write 1.0/2 solve that?

Or (hypothetical) float.divide(1, 2) if you want to be even more
explicit :-)


> Programming languages should do what they are told, and very little
> more.

Okay, now I'm confused. How is 1/2 returning 0.5 the language not doing what
you've told it to do?


> They should not wander off on surprising jaunts of their own
> invention out of the control of the programmer. It should be possible
> to know and understand the language, or at least the subset of it
> that you are likely to need for your everyday purposes. Floats are
> generally not understood, so they shouldn't be suddenly turning up
> un-called for.

How are they uncalled for?

> Python generally sticks to this idea very well, which is one of the
> things that I think make it an excellent programming language, so it
> is a shame that in the Python 2 to Python 3 change when mistakes were
> being rectified, a new one was introduced.

*shrug*

I've programmed in Python using classic integer division and true division,
and in my experience and opinion, classic division is a real pain to work
with. You're forever having to cast things to float or write .0 literals
just to convince the interpreter to do division the way you expect.

I suppose some language some day might experiment with swapping the
operators, so that a/b is integer division and a//b is true division.



--
Steven

Rustom Mody

unread,
May 22, 2016, 11:26:54 AM5/22/16
to
On Sunday, May 22, 2016 at 8:28:39 PM UTC+5:30, Marko Rauhamaa wrote:
> Python ought to be the perfect language for seasoned experts. It doesn't
> need to be dumbed down for noobs.

There's a language you may have heard of that you'll LOVE -- C++
Or maybe Haskell

On a somewhat more serious note:
Speaking of Haskell there has recently been a spate of dissent in the Haskell
with people like Mark Lenctzer, Eric Meijer etc saying they are quitting Haskell
because its too hard to teach.
[These names in roughly python-equivalents are like say Raymond Hettinger and Nick Coghlan]

I'd say python has done an eminently decent job so far in being approachable
+powerful.
But of late its losing the edge in the noob-side at the cost of catering to
cognoscenti.

IMHO Pascal got things right (wrt pedagogy) that are wronger and wronger in the last 30 years; see

http://blog.languager.org/2015/06/functional-programming-moving-target.html

On a different note: MIT has replaced scheme by python.
Cause to celebrate??
Depends... If being able to do more with less understanding is good... well maybe

Chris Angelico

unread,
May 22, 2016, 11:32:30 AM5/22/16
to
On Mon, May 23, 2016 at 1:19 AM, Steven D'Aprano <st...@pearwood.info> wrote:
> Okay, now I'm confused. How is 1/2 returning 0.5 the language not doing what
> you've told it to do?

That isn't the problem. With binary floats, 1/2 can be perfectly
represented, so you have no trouble anywhere. The problem comes when
you then try 1/5. What do you get? 3602879701896397/18014398509481984.
Python shows that as 0.2. Then you do some more arithmetic, and the
veil is pierced, and you discover that 1/5 doesn't actually return
0.2, but just something really really close to it - which it tells you
is 0.2.

I'm not saying that having 1/5 return 0 is better, but I'd like a
broad acceptance that 0.2 is imperfect - that, in fact, *every* option
is imperfect.

ChrisA

Marko Rauhamaa

unread,
May 22, 2016, 11:51:12 AM5/22/16
to
Chris Angelico <ros...@gmail.com>:
Ah, that reminds me of an ancient joke:

Ask an engineer what is two times two. He'll take out his slide rule,
quickly move the slider and reply: "Approximately four."

I remember learning my first programming language, Basic, when I was 16.
One of the very first things to notice was the way "the computer" worked
with approximations.


Marko

Jon Ribbens

unread,
May 22, 2016, 11:56:30 AM5/22/16
to
On 2016-05-22, Steven D'Aprano <st...@pearwood.info> wrote:
> On Mon, 23 May 2016 12:15 am, Jon Ribbens wrote:
>> Yes, it should return an integer - and not because I think Python
>> should behave like C on principle, but because:
>>
>> Explicit is better than implicit.
>> Simple is better than complex.
>> Complex is better than complicated.
>>
>> and floats are complicated.
>
> How is this any better though? Complicated or not, people want to divide 1
> by 2 and get 0.5. That is the functional requirement. Furthermore, they
> want to use the ordinary division symbol / rather than having to import
> some library or call a function.

That's a circular argument. You're defining the result as the
requirement and then saying that proves the result is necessary.
Clearly, people managed when 1/2 returned 0, and continue to do so
today in Python 2 and other languages.

> Having 1/2 return 0 (as Python 2 does by default) doesn't make the
> language any less complicated. It doesn't avoid the complexity of
> floats, it merely breaks the principle of least surprise,

No, it *adheres* to the principle of least surprise. Floats appearing
out of nowhere is surprising. Python 2's behaviour adhered to the
principle, and Python 3's breaks it.

>> That's a trap for those people though - it lulls them into thinking
>> that they understand what's going on, when in fact they don't,
>> because they don't understand floats, because almost nobody
>> understands floats. So they don't understand their program, and
>> - even worse - they don't know that they don't understand it.
>
> And how does forcing them to write 1.0/2 solve that?

Because it forces them to consciously address the fact that they are
asking for, and getting, floats, and that floats are not something
the language is willingly to silently foist upon them.

>> Programming languages should do what they are told, and very little
>> more.
>
> Okay, now I'm confused. How is 1/2 returning 0.5 the language not doing what
> you've told it to do?

I didn't ask for floats, I got floats. That's how.

>> They should not wander off on surprising jaunts of their own
>> invention out of the control of the programmer. It should be possible
>> to know and understand the language, or at least the subset of it
>> that you are likely to need for your everyday purposes. Floats are
>> generally not understood, so they shouldn't be suddenly turning up
>> un-called for.
>
> How are they uncalled for?

By... not being called for? I must admit I don't entirely understand
your question.

Chris Angelico

unread,
May 22, 2016, 12:35:46 PM5/22/16
to
On Mon, May 23, 2016 at 1:52 AM, Jon Ribbens
<jon+u...@unequivocal.co.uk> wrote:
> That's a circular argument. You're defining the result as the
> requirement and then saying that proves the result is necessary.
> Clearly, people managed when 1/2 returned 0, and continue to do so
> today in Python 2 and other languages.
>

Python's int and float types are both approximations to a
non-representable type called a "real number". You learned about
numbers in your early childhood - you learned about the basic concepts
like addition (you have one apple here and one apple there, so you
have two apples), and division (you take those two apples and split
them between four people by cutting them both in half). If you ask
someone how much apple everyone gets when you divide one apple between
two people, the answer should be "half an apple". Not "no apples" - of
course there are situations where things are indivisible, but numbers
themselves aren't, because there are times when you can indeed halve
those apples just fine.

The problem is that computers can't actually represent real numbers.
We have to content ourselves with a variety of approximations, which
we call numeric data types. Python then treats those data types as
being a minor detail, and the underlying real number as important:

>>> 1 == 1.0 == (1+0j)
True
>>> {1.0: "foo"}[1]
'foo'

Now, there is the small problem that the numeric types can't be
arranged into a perfect tower. If Python's integer were restricted to
2**32, you could automatically upcast any integer to a float
losslessly, and you can already losslessly upcast a float to a complex
simply by adding 0j to it. But most people don't work with numbers big
enough to be unrepresentable in 64-bit IEEE floating point:

>>> (1<<53)+1
9007199254740993
>>> (1<<53)+1 == (1<<53)
False
>>> (1<<53)+1.0 == (1<<53)
True

So for *most people*, this treatment works perfectly. An int will
upcast to a float when you apply the division operator to it. An int
or float will upcast to complex when you apply the exponentiation
operator:

>>> (-4)**0.5
(1.2246467991473532e-16+2j)

Nearly everything stored in a computer is an abstraction that can
leak. In this case, we can't perfectly represent real numbers or
calculate with them, so we do the best we can. Binary floating point
is far from perfect in purity, but it's not bad in practicality.
Remind me what PEP 20 says about that? Gotcha.

ChrisA

Jon Ribbens

unread,
May 22, 2016, 12:50:11 PM5/22/16
to
On 2016-05-22, Chris Angelico <ros...@gmail.com> wrote:
> Python's int and float types are both approximations to a
> non-representable type called a "real number".

Sorry, I have to stop you there as the entire premise of your post is
clearly wrong. "int" is not "an approximation of real numbers", it's
a model of the mathematical concept "integers", and it's not an
approximation, and since the long/int unification you can't even
overflow it as I understand things (barring ridiculous situations like
running out of memory).

Rustom Mody

unread,
May 22, 2016, 1:22:59 PM5/22/16
to
On Sunday, May 22, 2016 at 10:20:11 PM UTC+5:30, Jon Ribbens wrote:
Well maybe Chris should have said (or meant to say?)

In math:
ℤ ⊆ ℝ

whereas in programming int and float are disjoint types.

So structurally the (int,float) type pair poorly approximates the
(ℤ, ℝ) pair of math sets

Doesnt mean I agree with

> we can't perfectly represent real numbers or calculate with them, so we do
> the best we can

Floats are a grotesque travesty of ℝ
At the least, interval arithmetic can help automatically do the numerical
analysis for you.
Then there are all kinds of rational approximations like continued fractions
which are better than ℚ
All the way to "computable real numbers"

We're stuck with them because that's the hardware we've got.
Nothing intrinsic or necessary about it

Random832

unread,
May 22, 2016, 1:25:43 PM5/22/16
to
On Sun, May 22, 2016, at 10:58, Marko Rauhamaa wrote:
> Scheme has the best of both worlds:
>
> scheme@(guile-user)> 2/3
> $1 = 2/3
> scheme@(guile-user)> (exact->inexact $1)
> $2 = 0.6666666666666666

Why shouldn't Python do this?

Imagine some future version of Python:
>>> x = 2/3
>>> x
(2/3)
>>> type(x)
<class 'rational'>
# if it's going to be so integrated into the language it's
# hardly sensible to keep calling it 'fractions.Fraction'
>>> float(x)
0.6666666666666666

On Sun, May 22, 2016, at 11:52, Jon Ribbens wrote:
> No, it *adheres* to the principle of least surprise. Floats appearing
> out of nowhere is surprising. Python 2's behaviour adhered to the
> principle, and Python 3's breaks it.

Disregarding for the moment the particular imperfections of the float
representation (which would be gone if we used Fraction instead), this
is only true if the concrete types of results are regarded as part of
the result rather than as an implementation detail for how best to
return the requested value.

I think it would be entirely reasonable for Fractions to not only appear
out of nowhere, but to *disappear* when an operation on them yields a
value which is an integer.

Values are more important than types. Types are less important than
values.

Random832

unread,
May 22, 2016, 1:30:55 PM5/22/16
to
On Sun, May 22, 2016, at 12:46, Jon Ribbens wrote:
> Sorry, I have to stop you there as the entire premise of your post is
> clearly wrong. "int" is not "an approximation of real numbers", it's
> a model of the mathematical concept "integers",

It is a representation of Z, a subset of R (as is float, technically,
though that particular subset has no nice name like Z and Q) The
operators that apply to it are the operations on R, even operations
under which Z (or even R) is not closed.

Rustom Mody

unread,
May 22, 2016, 1:35:10 PM5/22/16
to
On Sunday, May 22, 2016 at 10:55:43 PM UTC+5:30, Random832 wrote:
> Values are more important than types. Types are less important than
> values.

A stronger version that I occasionally tell my students:
Values are in reality
Types are in our heads

Unfortunately we only know how to think thoughts inside our heads
Which means we are stuck with the imperfections of our thinking apparatus

Jon Ribbens

unread,
May 22, 2016, 1:58:59 PM5/22/16
to
On 2016-05-22, Random832 <rand...@fastmail.com> wrote:
> On Sun, May 22, 2016, at 12:46, Jon Ribbens wrote:
>> Sorry, I have to stop you there as the entire premise of your post is
>> clearly wrong. "int" is not "an approximation of real numbers", it's
>> a model of the mathematical concept "integers",
>
> It is a representation of Z, a subset of R

Yes, that's what I just said. "Z" is just (an approximation of!)
a symbol that means "the set of integers".

> (as is float, technically, though that particular subset has no nice
> name like Z and Q) The operators that apply to it are the operations
> on R, even operations under which Z (or even R) is not closed.

No, in Python integers are closed under the standard arithmetic
operators (+ - * / % **) - except, since Python 3, for "/", which
is now a special case.

Jon Ribbens

unread,
May 22, 2016, 2:10:08 PM5/22/16
to
On 2016-05-22, Random832 <rand...@fastmail.com> wrote:
> On Sun, May 22, 2016, at 11:52, Jon Ribbens wrote:
>> No, it *adheres* to the principle of least surprise. Floats appearing
>> out of nowhere is surprising. Python 2's behaviour adhered to the
>> principle, and Python 3's breaks it.
>
> Disregarding for the moment the particular imperfections of the float
> representation (which would be gone if we used Fraction instead), this
> is only true if the concrete types of results are regarded as part of
> the result rather than as an implementation detail for how best to
> return the requested value.
>
> I think it would be entirely reasonable for Fractions to not only appear
> out of nowhere, but to *disappear* when an operation on them yields a
> value which is an integer.
>
> Values are more important than types. Types are less important than
> values.

This would be true if we had some Grand Unified Lossless Number Type.
Unfortunately, we don't, and we're not likely to any time soon.

Random832

unread,
May 22, 2016, 2:15:05 PM5/22/16
to
On Sun, May 22, 2016, at 13:55, Jon Ribbens wrote:
> No, in Python integers are closed under the standard arithmetic
> operators (+ - * / % **)

Z is not closed under standard division, as surely as N isn't closed
under subtraction and R isn't closed under exponentiation. That is a
mathematical fact, not one about any particular language. What you are
saying is that Python 2's "/" is _not_ standard division (you want to
talk about the principle of least surprise...), and is therefore _not_ a
standard arithmetic operation. It's not Euclidean division, either,
since it gives a negative remainder for negative divisors.

Random832

unread,
May 22, 2016, 2:17:33 PM5/22/16
to
On Sun, May 22, 2016, at 14:06, Jon Ribbens wrote:
> This would be true if we had some Grand Unified Lossless Number Type.
> Unfortunately, we don't, and we're not likely to any time soon.

Scheme manages fine without one. It uses lossless types where it can,
and lets you detect that an "inexact" number (which could be float, or
could be any of the other types with an inexact flag set) was used where
it can't.

Ben Bacarisse

unread,
May 22, 2016, 3:51:31 PM5/22/16
to
Jon Ribbens <jon+u...@unequivocal.co.uk> writes:

<snip>
> No, in Python integers are closed under the standard arithmetic
> operators (+ - * / % **) - except, since Python 3, for "/", which
> is now a special case.

2 ** -1 is 0.5 even in Python 2[*].

I agree with your general point (that floats should not pop up unbidden)
but I don't think you need to exclude the possibly that an operator can
do that. With perfect hindsight, I think I'd have had the integers
closed under operators +, -, *, //, % and (say) ^, whilst making it
clear that / and ** produce floats. There's no reason to see this as
being any less explicit that writing 1.0 as a way to make your intent to
use floats explicit.

* Not a Python expert so all I means is that I get 0.5 on my machine and
I'm assuming that's what Python 2 mandates as the result.

--
Ben.

Steven D'Aprano

unread,
May 22, 2016, 6:09:19 PM5/22/16
to
On Mon, 23 May 2016 01:52 am, Jon Ribbens wrote:

> On 2016-05-22, Steven D'Aprano <st...@pearwood.info> wrote:

>> How is this any better though? Complicated or not, people want to divide
>> 1 by 2 and get 0.5. That is the functional requirement. Furthermore, they
>> want to use the ordinary division symbol / rather than having to import
>> some library or call a function.
>
> That's a circular argument. You're defining the result as the
> requirement and then saying that proves the result is necessary.
> Clearly, people managed when 1/2 returned 0, and continue to do so
> today in Python 2 and other languages.

I'm not defining the result. 4000+ years of mathematics defines the result.

If you get up off your chair and wander around and ask people other than C
programmers "What's one divide by two?", I am confident that virtually zero
percent will answer "zero".

People only managed when 1/2 returned 0 by *working around the problem*, and
yes, it is a problem. They work around it by explicitly casting values to
float (which, if carelessly done, just introduces new problems), or by
using "from __future__ import division".


>> Having 1/2 return 0 (as Python 2 does by default) doesn't make the
>> language any less complicated. It doesn't avoid the complexity of
>> floats, it merely breaks the principle of least surprise,
>
> No, it *adheres* to the principle of least surprise. Floats appearing
> out of nowhere is surprising. Python 2's behaviour adhered to the
> principle, and Python 3's breaks it.

The float isn't appearing out of nowhere. It appears because you're
performing a division.

When you call `len("hello world")`, are you shocked that an int appears out
of nowhere? Of course not. That's what len() does.

Why should you be shocked that division returns a fractional quantity?
That's what division does! Divide a cake into two pieces, and you have two
half cakes, not no cake.


>>> That's a trap for those people though - it lulls them into thinking
>>> that they understand what's going on, when in fact they don't,
>>> because they don't understand floats, because almost nobody
>>> understands floats. So they don't understand their program, and
>>> - even worse - they don't know that they don't understand it.
>>
>> And how does forcing them to write 1.0/2 solve that?
>
> Because it forces them to consciously address the fact that they are
> asking for, and getting, floats, and that floats are not something
> the language is willingly to silently foist upon them.

It does no such thing. Have you met any programmers? It forces them to add
an extraneous .0 to the end of their value, and give it no further thought
until somebody reports a bug that Fraction calculations are silently
coerced to floats, and that Decimal calculations raise an exception. And
then they close the bug report "Will not fix" because it's too hard.

>>> Programming languages should do what they are told, and very little
>>> more.
>>
>> Okay, now I'm confused. How is 1/2 returning 0.5 the language not doing
>> what you've told it to do?
>
> I didn't ask for floats, I got floats. That's how.

You performed a division. What did you expect, a dict?


>>> They should not wander off on surprising jaunts of their own
>>> invention out of the control of the programmer. It should be possible
>>> to know and understand the language, or at least the subset of it
>>> that you are likely to need for your everyday purposes. Floats are
>>> generally not understood, so they shouldn't be suddenly turning up
>>> un-called for.
>>
>> How are they uncalled for?
>
> By... not being called for? I must admit I don't entirely understand
> your question.

You performed a division. By definition, this involves returning a
fractional amount, or at least the possibility of returning a fractional
amount. To say that it is "uncalled for" to receive a fractional amount is,
frankly, bizarre.



--
Steven

Ian Kelly

unread,
May 22, 2016, 7:05:07 PM5/22/16
to
If you want Python integers to be closed under division *and* be
mathematically correct then the result of 1 / 2 should be the
multiplicative inverse of 2, which is *undefined* in Z. While that
might be an argument for raising an exception, it's not in any way a
justification of returning 0.

Jon Ribbens

unread,
May 22, 2016, 8:38:48 PM5/22/16
to
On 2016-05-22, Ben Bacarisse <ben.u...@bsb.me.uk> wrote:
> Jon Ribbens <jon+u...@unequivocal.co.uk> writes:
><snip>
>> No, in Python integers are closed under the standard arithmetic
>> operators (+ - * / % **) - except, since Python 3, for "/", which
>> is now a special case.
>
> 2 ** -1 is 0.5 even in Python 2[*].

Haha, excellent, well found. I was wondering if there were any edge
cases I was wrong about. I suppose ideally I would make it so that
2 ** -1 throws an exception or something. But of course this
particular train has left the station a long time ago.

> I agree with your general point (that floats should not pop up unbidden)
> but I don't think you need to exclude the possibly that an operator can
> do that. With perfect hindsight, I think I'd have had the integers
> closed under operators +, -, *, //, % and (say) ^, whilst making it
> clear that / and ** produce floats. There's no reason to see this as
> being any less explicit that writing 1.0 as a way to make your intent to
> use floats explicit.

My fundamental point is that floats are surprising, so people should
not be surprised by them arriving unbidden - and most of the time,
there is no need at all for them to turn up unannounced. Making that
occurrence more likely rather than less was a mistake.

Jon Ribbens

unread,
May 22, 2016, 8:40:10 PM5/22/16
to
On 2016-05-22, Steven D'Aprano <st...@pearwood.info> wrote:
> On Mon, 23 May 2016 01:52 am, Jon Ribbens wrote:
>> On 2016-05-22, Steven D'Aprano <st...@pearwood.info> wrote:
>>> How is this any better though? Complicated or not, people want to divide
>>> 1 by 2 and get 0.5. That is the functional requirement. Furthermore, they
>>> want to use the ordinary division symbol / rather than having to import
>>> some library or call a function.
>>
>> That's a circular argument. You're defining the result as the
>> requirement and then saying that proves the result is necessary.
>> Clearly, people managed when 1/2 returned 0, and continue to do so
>> today in Python 2 and other languages.
>
> I'm not defining the result. 4000+ years of mathematics defines the result.

OK, I'm bored of you now. You clearly are not willing to imagine
a world beyond your own preconceptions. I am not saying that my view
is right, I'm just saying that yours is not automatically correct.
If you won't even concede that much then this conversation is pointless.

Chris Angelico

unread,
May 22, 2016, 9:01:58 PM5/22/16
to
The point of arithmetic in software is to do what mathematics defines.
Would you expect 1+2 to return 5? No. Why not? Where was the result
defined?

ChrisA

Jon Ribbens

unread,
May 22, 2016, 9:04:01 PM5/22/16
to
Are you trying to compete with him for the Missing The Point Award?

bream...@gmail.com

unread,
May 22, 2016, 9:47:33 PM5/22/16
to
We had the RUE, now we've got the Resident Arithmetic Expert or RAE. Just what the doctor didn't order.

MRAB

unread,
May 22, 2016, 9:54:54 PM5/22/16
to
The relevant doc is PEP 238, dating to March 2001, when Python 2.2 was new.

Ben Finney

unread,
May 23, 2016, 12:29:27 AM5/23/16
to
Jon Ribbens <jon+u...@unequivocal.co.uk> writes:

> OK, I'm bored of you now. You clearly are not willing to imagine
> a world beyond your own preconceptions.

Steven has, in the message to which you responded, asked for you to
*describe* this other world you assert exists.

More concretely: Steven is not denying someone might have different
expectations. On the contrary, you've said your expectations differ, and
Steven is *explicitly asking* you to specify those expectations.

And, instead of answering, you give this dismissal. Are your
expectations so hard to describe?

--
\ “It is wrong to think that the task of physics is to find out |
`\ how nature *is*. Physics concerns what we can *say* about |
_o__) nature…” —Niels Bohr |
Ben Finney

Rustom Mody

unread,
May 23, 2016, 2:09:45 AM5/23/16
to
On Monday, May 23, 2016 at 9:59:27 AM UTC+5:30, Ben Finney wrote:
> Jon Ribbens writes:
>
> > OK, I'm bored of you now. You clearly are not willing to imagine
> > a world beyond your own preconceptions.
>
> Steven has, in the message to which you responded, asked for you to
> *describe* this other world you assert exists.
>
> More concretely: Steven is not denying someone might have different
> expectations. On the contrary, you've said your expectations differ, and
> Steven is *explicitly asking* you to specify those expectations.
>
> And, instead of answering, you give this dismissal. Are your
> expectations so hard to describe?

Steven is making wild and disingenuous statements; to wit:

On Monday, May 23, 2016 at 3:39:19 AM UTC+5:30, Steven D'Aprano wrote:
> I'm not defining the result. 4000+ years of mathematics defines the result.

This is off by on order of magnitude.
Decimal point started with Napier (improving on Stevin): 17th century
OTOH it is plain numbers (ℕ) that have been in use for some 4 millennia.

>
> If you get up off your chair and wander around and ask people other than C
> programmers "What's one divide by two?", I am confident that virtually zero
> percent will answer "zero".

You forget that we (most of us?) went to school.
My recollections of it -- ok maybe fogged by near 5 decades:

I first learnt something called 'long-division'
In that procedure you take 2 numbers called divisor and dividend
And GET TWO NUMBERS a quotient and a remainder.
[At that point only knew of the numbers we would later call ℤ (or was it ℕ --
not sure -- decimal point would come later]

Later (again dont remember order) we were taught
- short division
- decimal numbers
[I mention short division -- put numerator on top of denominator and cancel off
factors -- because the symmetry of numerator:denominator and quotient:remainder
is more apparent there than in long-division]

In any case if learning primacy has any significance, pure integer division
is more basic than decimal number division.

To recapitulate the situation:
Mathematics (mathematicians if you prefer) have a strong attachment to
to two nice properties of operators:

The first is obvious and unarguable -- totality
The second does not have a standard term but is important enough -- I will
call it 'homogeneity'. By this I mean a type of the form: t × t → t

Its nice to have totality because one can avoid case-analysing:
f(x) when x ∈ domain(f)

Its nice to have homogeneity because homogeneous operators can be
nested/unnested/played-with
ie for ◼ : t × t → t
x ◼ y ◼ z makes sense this way x ◼ (y ◼ z) or this way (x ◼ y) ◼ z
With non-homogeneous ◼ these may not make sense.


- Choosing ÷ to be total and homogeneous necessitates widening
ℤ (or ℕ) to ℚ or ℝ (or something as messy)
- Choosing ÷ to be non-homogeneous means needing to deal with quotients and
remainders
Cant write if (x/4 < 256)...
have to write
quot, rem = x/4 # throw away rem
if quot < 256: ...


Haskell has (almost) what I learnt at school:

Prelude> let (q,r) = 7 `divMod` 3
Prelude> (q,r)
(2,1)

Replace the strange `divMod` with / and we are back to the behavior I first
learnt at school


So with some over-simplification:
- the first choice leads to numerical analysis
- the second leads to number theory

To say that one is natural --especially the one that chooses something other
than natural numbers! --and the other is surprising is nonsense.

Marko Rauhamaa

unread,
May 23, 2016, 2:31:08 AM5/23/16
to
Rustom Mody <rusto...@gmail.com>:

> Haskell has (almost) what I learnt at school:
>
> Prelude> let (q,r) = 7 `divMod` 3
> Prelude> (q,r)
> (2,1)

Python:

>>> divmod(7, 3)
(2, 1)

> Replace the strange `divMod` with / and we are back to the behavior I
> first learnt at school

X
We never used '/' in school for anything. We used 'X : Y' or '---'.
Y

Anyway, every calculator in the world produces:

1
÷
2
=

==> 0.5


Marko

Rustom Mody

unread,
May 23, 2016, 2:46:39 AM5/23/16
to
On Monday, May 23, 2016 at 12:01:08 PM UTC+5:30, Marko Rauhamaa wrote:
> Rustom Mody :
Not true:
https://www.youtube.com/watch?v=iynCW9O_x58
[And ive seen such 40 years ago]

Steven D'Aprano

unread,
May 23, 2016, 3:10:11 AM5/23/16
to
On Monday 23 May 2016 03:25, Random832 wrote:

> Why shouldn't Python do this?
>
> Imagine some future version of Python:
>>>> x = 2/3
>>>> x
> (2/3)
>>>> type(x)
> <class 'rational'>


You would have a lot of trouble convincing Guido that this was a good idea,
because that's what ABC used to do, and it was a performance killer, as well as
being horrible to work with.

Rationals like 2/3 are fine. But the trouble is, by the time you've done a
handful of calculations, you've probably got something like

5529748264768821/18014398509481984

and after a few dozen calculations you might have something like:

391303115027894573050315966944902650883330501592661514878103414119479331609846605406995557395414953/14109433351889544757158673468211253755455976544516272594484740551000078367597300074881469573563940864

and it just keeps getting worse and worse.


For bonus points, without converting to floats, can you tell which of of the
two numbers is bigger by sight?



--
Steve

Steven D'Aprano

unread,
May 23, 2016, 4:10:02 AM5/23/16
to
On Monday 23 May 2016 16:09, Rustom Mody wrote:

> Steven is making wild and disingenuous statements; to wit:
>
> On Monday, May 23, 2016 at 3:39:19 AM UTC+5:30, Steven D'Aprano wrote:
>> I'm not defining the result. 4000+ years of mathematics defines the result.
>
> This is off by on order of magnitude.
> Decimal point started with Napier (improving on Stevin): 17th century
> OTOH it is plain numbers (ℕ) that have been in use for some 4 millennia.

Are you saying that the Egyptians, Babylonians and Greeks didn't know how to
work with fractions?

http://mathworld.wolfram.com/EgyptianFraction.html

http://nrich.maths.org/2515

Okay, it's not quite 4000 years ago. Sometimes my historical sense of the
distant past is a tad inaccurate. Shall we say 2000 years instead?


>> If you get up off your chair and wander around and ask people other than C
>> programmers "What's one divide by two?", I am confident that virtually zero
>> percent will answer "zero".
>
> You forget that we (most of us?) went to school.

Er, why would I forget that? That's the point -- people have learned about
fractions. I didn't say "go off deep into the Amazonian rainforests, or into
the New Guinea highlands, and ask innumerate hunter gatherers...".

But even innumerate hunter gatherers will have an understanding that if you
have one yam which you wish to share between two people, they will each get
half. Not zero.



--
Steve

Steven D'Aprano

unread,
May 23, 2016, 4:34:12 AM5/23/16
to
On Monday 23 May 2016 10:36, Jon Ribbens wrote:

> OK, I'm bored of you now. You clearly are not willing to imagine
> a world beyond your own preconceptions. I am not saying that my view
> is right, I'm just saying that yours is not automatically correct.
> If you won't even concede that much then this conversation is pointless.

I borrowed Guido's Time Machine, and wrote the following

I suppose some language some day might experiment with swapping the
operators, so that a/b is integer division and a//b is true division.

*nine hours* before your response above. But perhaps that was explicit enough,
so if it will satisfy you, I will state for the record that defining the /
operator to do integer division is not necessarily bad.

But it has been roundly and broadly rejected by the Python community. It was
tried and rejected. One way or another, your opinion is a minority view.


--
Steve

Ian Kelly

unread,
May 23, 2016, 10:15:22 AM5/23/16
to
On Mon, May 23, 2016 at 2:09 AM, Steven D'Aprano
<steve+comp....@pearwood.info> wrote:
> Are you saying that the Egyptians, Babylonians and Greeks didn't know how to
> work with fractions?
>
> http://mathworld.wolfram.com/EgyptianFraction.html
>
> http://nrich.maths.org/2515
>
> Okay, it's not quite 4000 years ago. Sometimes my historical sense of the
> distant past is a tad inaccurate. Shall we say 2000 years instead?

Those links give dates of 1650 BC and 1800 BC respectively, so I'd say
your initial guess was closer.

Ben Bacarisse

unread,
May 23, 2016, 10:29:47 AM5/23/16
to
Right, but this is to miss the point. Let's say that 4000 years have
defined 1/3 to be one third, but Python 3 (as do many programming
languages) defines 1/3 to be something very very very very close to one
third, and *that* idea is very very very very new! It's unfortunate
that the example in this thread does not illustrate the main problem of
shifting to binary floating point, because 1/2 happens to be exactly
representable.

--
Ben.

Ian Kelly

unread,
May 23, 2016, 10:50:10 AM5/23/16
to
I'm not going to dig back through the thread, but my recollection is
that's exactly why that example was chosen. Since 1/2 can be
represented exactly as a float, it *should* be represented as a float.
Picking another value (0) that isn't even close to the exact value of
1/2 isn't helping anybody.

Chris Angelico

unread,
May 23, 2016, 10:57:24 AM5/23/16
to
On Tue, May 24, 2016 at 12:29 AM, Ben Bacarisse <ben.u...@bsb.me.uk> wrote:
> Right, but this is to miss the point. Let's say that 4000 years have
> defined 1/3 to be one third, but Python 3 (as do many programming
> languages) defines 1/3 to be something very very very very close to one
> third, and *that* idea is very very very very new!

Have you ever written one third as 0.33333333 ? Because that's also
something very very close to one third.

ChrisA

Rustom Mody

unread,
May 23, 2016, 11:31:10 AM5/23/16
to
On Monday, May 23, 2016 at 7:59:47 PM UTC+5:30, Ben Bacarisse wrote:
> Ian Kelly writes:
>
> > On Mon, May 23, 2016 at 2:09 AM, Steven D'Aprano wrote:
> >> Are you saying that the Egyptians, Babylonians and Greeks didn't know how to
> >> work with fractions?
> >>
> >> http://mathworld.wolfram.com/EgyptianFraction.html
> >>
> >> http://nrich.maths.org/2515
> >>
> >> Okay, it's not quite 4000 years ago. Sometimes my historical sense of the
> >> distant past is a tad inaccurate. Shall we say 2000 years instead?
> >
> > Those links give dates of 1650 BC and 1800 BC respectively, so I'd say
> > your initial guess was closer.
>
> Right, but this is to miss the point. Let's say that 4000 years have
> defined 1/3 to be one third, but Python 3 (as do many programming
> languages) defines 1/3 to be something very very very very close to one
> third, and *that* idea is very very very very new! It's unfortunate
> that the example in this thread does not illustrate the main problem of
> shifting to binary floating point, because 1/2 happens to be exactly
> representable.

Yes the point is being missed but in a different direction:
The SET (as a completed whole) of real numbers (ℝ) is no more than a 100 years
old.
People may have used fractions earlier

And even here the first line of Steven's http://nrich.maths.org/2515 says
"Did you know that fractions as we use them today didn't exist in Europe until the 17th century?"

Egypt and Babylon (and India for that matter) are really only of archaeological
interest in the sense that there is almost complete loss of continuity
from then to now

That the set ℝ legitimately exists was a minority view -- Cantor,Dedekind,
Weierstrass...

On the other side Kronecker belligerently declared:
"The good Lord made the natural numbers (Zahlen in German)
All the rest is the work of man"

This was the MAINSTREAM view in the 1880s.

As late as 1918 Weyl and Polya took a bet that math concepts such as
real numbers, sets, countability etc would be relegated to history as a bad
dream and the pristine purity of constructive math would be firmly established
-- where "constructive math" basically means ℕ is the only reasonable infinite set and that ℝ is anything but real!

https://en.wikipedia.org/wiki/Hermann_Weyl#Foundations_of_mathematics

For a conspectus showing that:
- our current views are fairly recent (compared to Egypt, Babylon etc)
- that far from being universally accepted they were hotly disputed
- And thence gave rise to our field of CS
see http://blog.languager.org/2015/03/cs-history-0.html

Jon Ribbens

unread,
May 23, 2016, 11:39:23 AM5/23/16
to
Who's that then? Maybe they could chip in with their opinion.

Steven D'Aprano

unread,
May 23, 2016, 11:47:38 AM5/23/16
to
For many purposes, 0.33 is close enough to one third.



--
Steven

Chris Angelico

unread,
May 23, 2016, 11:57:33 AM5/23/16
to
As is 3.14 close enough to π. (Or 256/81 or any of the other
historical values.) Approximations are nothing new.

ChrisA

Ian Kelly

unread,
May 23, 2016, 12:03:45 PM5/23/16
to
On Mon, May 23, 2016 at 9:53 AM, Ian Kelly <ian.g...@gmail.com> wrote:
> I'm not sure where ℝ comes into this in the first place. Existing
> Python numeric types only represent various subsets of ℚ (in the case
> of fractions.Fraction, the entirety of ℚ).

And of course I realized after sending that I forgot about complex
numbers. But even there Python merely represents 2-tuples of ℚ.

Rob Gaddi

unread,
May 23, 2016, 12:48:25 PM5/23/16
to
Marko Rauhamaa wrote:

> Christopher Reimer <christoph...@icloud.com>:
>
>> Under various proposals in the U.S., everyone will soon learn how to
>> program and/or become a computer scientist. Won't be long before some
>> snotty-nosed brat graduates from preschool, takes a look at your code,
>> and poops in his diapers. He will then dips his finger into his
>> diaper, write on the whiteboard how your code can be written in a
>> single line, and summary dismiss you with security escorting you off
>> the premises.
>>
>> Gotta love the future. :)
>
> Unfortunately, most CS graduates don't seem to know how to program.
>
> Yes, some highschoolers could excel in the post of a senior software
> engineer -- I've had the privilege of working alongside several
> specimens. However, it has been known for half a century that good
> developers are hard to come by.
>
> I think it is essential to learn the principles of programming just like
> it is essential to learn the overall principles of nuclear fission or be
> able to locate China on the map. However, a small minority of humanity
> will ever earn a living writing code.
>
> At the same time, it may be that in the not-too-distant future, the
> *only* jobs available will be coding jobs as we start to take the
> finishing steps of automating all manufacturing, transportation and
> services. Then, we will have a smallish class of overworked coders who
> have no use or time for money and vast masses of jobless party-goers who
> enjoy the fruits of the coders' labor.
>
>
> Marko

Well, so long as we're going wildly OT...

I think it's not a matter of who's going to earn a living by it. I
think it's that, increasingly, programming is similar to carpentry. I
can't reframe a house, and certainly can't build cabinetry, but I can do
an adequate job putting up a simple wooden shelf.

Looked at that way, it becomes a question of teaching people enough of
the general principles to be able to muddle though and do a passable job
on trivial "But all I want to do is" tasks. It's not a CS degree, it's
shop class.

--
Rob Gaddi, Highland Technology -- www.highlandtechnology.com

Email address domain is currently out of order. See above to fix.

Ben Bacarisse

unread,
May 23, 2016, 12:51:27 PM5/23/16
to
Chris Angelico <ros...@gmail.com> writes:

> On Tue, May 24, 2016 at 12:29 AM, Ben Bacarisse <ben.u...@bsb.me.uk> wrote:
>> Right, but this is to miss the point. Let's say that 4000 years have
>> defined 1/3 to be one third, but Python 3 (as do many programming
>> languages) defines 1/3 to be something very very very very close to one
>> third, and *that* idea is very very very very new!
>
> Have you ever written one third as 0.33333333 ?

Not that I recall, but, obviously, I can't be sure. I can't even tell
without counting how many 3s there are there. Why do you ask?

> Because that's also
> something very very close to one third.

Yes it is, but I don't get what point you are making.

--
Ben.
It is loading more messages.
0 new messages