Python Forum
For-else loop misbehavior? - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: General Coding Help (https://python-forum.io/forum-8.html)
+--- Thread: For-else loop misbehavior? (/thread-12772.html)



For-else loop misbehavior? - Johne1 - Sep-11-2018

REF: The Python Tutorial, 4.4. break and continue Statements, and else Clauses on Loops, 1st example code.

The example looks straight forward (no pun intended) until you insert code between the inner 'for' and 'if'.

To watch the variable values during execution, I added the debugging line:

print('n=' + str(n) + ', x=' + str(x))'

for n in range(2, 10):
    for x in range(2, n):
        print('n=' + str(n) + ', x=' + str(x))      
        if n % x == 0:
            print(n, 'equals', x, '*', n//x)
            break
    else:
        # loop fell through without finding a factor
        print(n, 'is a prime number')


Where's the output from, "print('n=' + str(n) + ', x=' + str(x)) ", before "2 is a prime number" ?!?!

Change the start value of 'for n in range(2,10)'

to a non-prime, i.e. 'for n in range(4,10)'

and now the 'print' line before the 'if' test works as expected.

The line to display the variable values should execute before the 'if' test in every iteration of the loop.

This illogical behavior ONLY occurs the very first time through the inner loop. Each successive time, the code executes as expected.

Never seen a 'for' loop behave like this in any other language. Is the 'else' clause somehow to blame?


RE: For-else loop misbehavior? - buran - Sep-11-2018

else part in the for loop is executed only if no break statement was executed.
So inner loop x will iterate over all integers from 2 to n-1, if n is divisible by x, break statement will be executed thus else part will not be executed.
in case n is prime, break statement will not be executed and so once inner loop finish it will executed also the else part.


RE: For-else loop misbehavior? - Johne1 - Sep-11-2018

Please try the sample code.

With 'for n in range(2,10)'
First line of output should always be: n=2, x=2 (this is never output!)

Again with 'for n in range(4,10)' (or any non-prime number as range start)
Output works as expected now! n=4, x=2

SAMPLE CODE:
for n in range(2, 10):
    for x in range(2, n):
        # ignored 1st-time through inner loop
        #  when n range starts on prime number 
        print('n=' + str(n) + ', x=' + str(x))
        if n % x == 0:
            print(n, 'equals', x, '*', n//x)
            break
    else:
        # loop fell through without finding a factor
        print(n, 'is a prime number')



RE: For-else loop misbehavior? - buran - Sep-11-2018

(Sep-11-2018, 07:55 PM)Johne1 Wrote: First line of output should always be: n=2, x=2 (this is never output!)
if n=2 then in the inner loop x will try to iterate over range(2,2). However range(2,2) is empty one:
>>> list(range(2,2))
[]
so the loop body (lines 3-8) is never executed (and thus no break statement) so else part (lines 10-11) is executed and it prints that 2 is prime number


RE: For-else loop misbehavior? - Johne1 - Sep-11-2018

>>> list(range(4,2))
[]
That list is empty, too, yet the inner loop hits my first print function
when I change outer loop to 'n in range(4,2)'.


RE: For-else loop misbehavior? - buran - Sep-11-2018

no, when n=4, the inner loop is
for x in range(2, 4):
i.e. the range is not empty
>>> list(range(2,4))
[2, 3]
so x will first be equal to 2. It will execute the print function on line 5, then the if condition (on line 6) will be evaluated to True, it will execute the print function on line 7, then execute break on line 8. Because the break was executed, the else part will not be executed


RE: For-else loop misbehavior? - Johne1 - Sep-11-2018

Thanks! I'm going to have to read up on the range() and list() functions to understand why range(2,2) is empty. I'm baffled.


RE: For-else loop misbehavior? - buran - Sep-11-2018

(Sep-11-2018, 09:00 PM)Johne1 Wrote: why range(2,2) is empty
range(start, stop, step) will give you the numbers from start to stop-1, i.e. stop is not included. start and step are optional,i.e.
range(n) is same as range(0, n, 1)

so range(2,2) start=2 and stop =2, that means it starts from 2 and should stop at 2-1, i.e. 1.
In other words, upper bound (1) is smaller than the lower bound (2).

Or if you prefer, range (assuming step is 1), is the intersection of the set of all integers greater or equal to start (i.e. greater or equal to 2) and the set of all numbers lower than stop (in this case lower than 2). The intersection is empty set.

list function in this case is only for presentation purpose. i.e. I use it just to show what range object is. in python3 range returns range object and if you print it, you will not get what numbers are included:
>>> range(2,4)
range(2, 4)
>>> list(range(2,4))
[2, 3]



RE: For-else loop misbehavior? - Johne1 - Sep-11-2018

Thanks, again! Great, simple explanation. I was over-thinking it, confusing it with a 2-dimensional array, instead of a simple list.