1

I have the code below, and for some reason the while loop is not terminating.

def iterator(i, f):
    print len(f)
    print i
    while i < len(f):
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        function()
    return f

The print statements are not really necessary, but the counter i gets increased by another function so I wanted to make sure it wasn't the problem. For a list f with 4 items, it prints like this:

4, 0, Restarted.
4, 1, iterate.
4, 2, iterate.
4, 3, iterate.
4, 4, iterate.
4, 4, iterate.
etc..

I don't understand why it keeps going into the while loop when i = 4 and len(f) = 4. It should be breaking the loop and executing the return function, but for some reason it doesn't.

Can anyone explain why a while loop will not terminate when its condition becomes false?

EDIT: Some code to better explain what is going on. I've also clarified that i changes by function(), which then calls the iterator with the increased i. Hope that makes sense.

f = [0,1,2,3]
i = 0

def iterator(i, f):
    print i
    while i < len(f):
        print i
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        function(i, f)
    return f


def function(i, f):
    i += 1
    iterator(i, f)

iterator(i,f)

The result of this is like:

0, 0, Restarted.
1, 1, iterate.
2, 2, iterate.
3, 3, iterate.
4, 3, iterate.
4, 3, iterate.
etc.
4
  • does function() call iterator() ? Commented Jul 1, 2013 at 13:56
  • Please post the code for function, or at least the relevant parts so that we can run the code and reproduce your results. Commented Jul 1, 2013 at 13:59
  • @HenryKeiter; Done, hope it is clearer now. Commented Jul 1, 2013 at 14:03
  • @Mythio much clear: you are mixing recursion and loop. Commented Jul 1, 2013 at 14:06

3 Answers 3

6

the counter i gets increased by another function

Impossible. it is a local integer. The values printed are values of a different variable. Try add print i inside the loop, and you'll see.

int is an immutable type. it means an object of this type cannot change its value. The only thing can change is the object that some variable is holding.

def inc(i):
    i+=1

i=0
inc(i)
print i

output:

0

why is this? because i inside inc and i outside it are two independent variable. The i+=1 inside inc only mean "Let the local variable i now point to a new object, 2". It does not affect the global i in any way. Just like C and Java (and the default in most other major languages), variables are passed by value.

The only way this loop can end is if function removes elements from f, assuming f is a list and therefore mutable.


Here's an equivalent code to your new version. note that you are using recursion, not only looping. and you will be out of stack soon:

f = [0,1,2,3]
i = 0

def iterator(i, f):
    print i
    while i < len(f):
        '''if the recursion is less than 4 levels deep: loop forever
           else: don't loop at all'''
        print i
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        iterator(i+1, f)
    return f

Output:

0, 0, Restarted. # 0 level recursion, 1st iteration
1, 1, iterate.   # 1 level recursion, 1st iteration
2, 2, iterate.   # 2 level recursion, 1st iteration
3, 3, iterate.   # 3 level recursion, 1st iteration
4, 3, iterate.   # 3 level recursion, 2nd iteration, the condition now is false
4, 3, iterate.   # 3 level recursion, 3rd iteration  
4, 3, iterate.   # 3 level recursion, 4th iteration
4, 3, iterate.   # 3 level recursion, 5th iteration 

and so on.

Sign up to request clarification or add additional context in comments.

7 Comments

What do you mean? The value i is supplied to the function everytime it is called, along with the list f. How can it be a different i in the condition?
But the value for i IS changing..the first print gives i = 0,1,2,3,4, if I put print i inside the while it gives i = 0,1,2,3...which should be correct, because it shouldn't go inside the while to print it when i = 4.
@Mythio put the code with the printing you are talking about. There is no way for a local variable to be changed this way.
@Mythio You're just calling the iterator() function multiple times by calling function() multiple times.
@Mythio oh, also, you're waiting for function() to return in iterator() and waiting for iterator() to return from function().
|
2

Your problem is that you're not correctly understanding global vs local variables. The name i means something different outside the loop than it does inside the loop. Try this to see what I mean:

def iterator(i, f):
    # Your code

def function(i, f):
    # Your code

iterator(0, [0, 1, 2, 3])

This still runs, of course, even though there's no i defined globally. That's because you've got function arguments named i, making i take on a new meaning inside your functions. Because numbers are immutable, anytime you "change" i inside the function, you're really just saying "okay, now i is going to point to a different number inside this function". You're not changing the underlying number.

There are two ways to fix your problem. The quick and dirty way is to remove the parameters from both functions. This will make it so that i only ever refers to the global variable i:

i = 0
f = range(4)
def iterator():
    # etc
def function():
    # etc
iterator()

The use of global variables like this is discouraged, though, because it's not clean, it's hard to debug, and other functions could get ahold of it and cause unexpected behavior... generally just not a great idea. Instead, you can keep your function signatures, but make use of return values to do whatever it is you want to do. Something like this:

global_f = [0,1,2,3]
global_i = 0

def iterator(i, f):
    while i < len(f):
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        i = function(i, f) # Reassign i or it won't be changed!!
    return f

def function(i, f):
    return i + 1

iterator(global_i, global_f)

Note that I'm not sure what function was supposed to do, so I simplified it to a plain return statement. Be careful when two functions call each other in general; it's very easy to get into infinite loops when doing recursion!

Comments

1

Local vs global

As others have mentioned, you're treating every variable named i as if it is the same variable, but it's not. If you remove the f = and i = lines from the top and call iterator(0, [0,1,2,3]) at the bottom instead, you'll get the same thing. In fact, it doesn't matter at all what you name these variables.

Look:

>>> i = 1
>>> def inc(i):
...     i += 1
... 
>>> inc(i)
>>> i
1
>>> k = 0
>>> inc(k)
>>> k
0
  1. It didn't matter what name I used for a variable. Whatever you pass as an argument is locally called i.
  2. The i in inc() just gives a local name to the object id passed into it. Its scope lasts so long as your program is inside the body of that function. If you don't return that variable, it disappears when your function returns.

So you may wonder if there even is such a thing as global scope. Yes, indeed: if you refer to a variable inside of a function without passing it, you'll get a NameError - unless it has scope above the function, like so:

>>> n = 10
>>> def inc(i):
...     i += 1
...     print n
... 
>>> inc(i)
10
>>> i
1

Now, that's not to say you can just do anything with this global variable. The following won't work:

>>> def inc(i):
...     i += 1
...     n += 1
... 
>>> inc(i)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in inc
UnboundLocalError: local variable 'n' referenced before assignment

In order to modify a global variable inside of a function, you need to declare it as global before you do that.

>>> def inc(i):
...     i += 1
...     global n
...     n += 1
... 
>>> inc(i)
>>> n
11

What you're actually passing to the functions

To make things more convoluted, you essentially call by value when passing integer arguments to Python. You are actually passing references to integer objects, but these objects are static:

>>> id(1)
140272990224888
>>> id(2)
140272990224864
>>> i = 1
>>> id(i)
140272990224888
>>> i += 1
>>> id(i)
140272990224864
>>> id(1)
140272990224888

When we incremented i, its id changed. The id of the integer object for 1 didn't change, the id for i did. This means that i is just a name for a pointer to an integer object, and when you increment i you just change its pointer to point to the integer object which has a value of one greater than the value of the integer object to which it was previously pointing.

So, even though you are passing object references, you're just passing the ids for the integer objects 0, 1, 2, 3, and 4. And, since you don't return those references, or declare them globally, they lose scope when your function returns.

Waiting for return

Another oversight you've made is that you assume that when iterator() returns, the program ends. When you call function() from inside the while loop in iterator() you are waiting for function() to return before you continue the loop. function() then calls iterator() which then calls function() which then calls... you get the idea. When you have your final loop with the 4, 3, iterate. nonsense return, you see yourself enter the 3, 3, iterate. loop again - but that loop never returns.

When you call another function from inside a function, you have to wait for that other function to return before you can continue to the next statement.

>>> def foo():
...     bar()
...     print "world!"
... 
>>> def bar():
...     print "hello"
... 
>>> foo()
hello
world!

foo() couldn't print "world!" until bar() returned. If you changed bar():

>>> def bar():
...     print "hello"
...     foo()
... 
>>> foo()
hello
hello
hello
hello
hello
hello
...
  File "<stdin>", line 3, in bar
  File "<stdin>", line 2, in foo
  File "<stdin>", line 3, in bar
  File "<stdin>", line 2, in foo
RuntimeError: maximum recursion depth exceeded
>>> 
KeyboardInterrupt

Woops. Neither function could return because it was waiting on a call to the other, and each time a function called the other a new stack from was created - and eventually the stack overflowed.

If you don't understand how the stack works, well, that's not in the scope of my (incredibly verbose) answer. You need to look that up.

Mixing control structures

By having two functions which call each other, you've created a recursive control structure. Additionally, by using a while loop without ever changing the loop condition, you've created an infinite loop. So while your recursion has a base case (i >= len(f) because i < len(f) is the recursive case), your infinite loop will cause the program to call that base case over and over.

What exactly is happening (the call stack)

  • iterator(0, [0,1,2,3]) calls and waits for
  • function(0, [0,1,2,3]) calls and waits for
  • iterator(1, [0,1,2,3]) calls and waits for
  • function(1, [0,1,2,3]) calls and waits for
  • iterator(2, [0,1,2,3]) calls and waits for
  • function(2, [0,1,2,3]) calls and waits for
  • iterator(3, [0,1,2,3]) calls and waits for
  • function(3, [0,1,2,3]) calls and waits for
  • iterator(4, [0,1,2,3]) returns
  • function(3, [0,1,2,3]) returns
  • iterator(3, [0,1,2,3]) loops, then calls and waits for
  • function(3, [0,1,2,3]) etc.

This is why you see 4, 3, iterate happening over and over: you print 4 in iterator(4, [0,1,2,3]), but don't begin the loop, so iterator(3, [0,1,2,3]) gets to loop and you print 3, iterate. and then you go back into iterator(4, [0,1,2,3]) which prints 4 again, and so on. Because iterator(4, [0,1,2,3]) returns, you don't get a stack overflow, but you do still get an infinite loop.

How to fix it

If you want recursion:

f = [0,1,2,3]
i = 0

def iterator(i, f):
    print i
    if i < len(f):
        print i
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        function(i, f)
    return f

def function(i, f):
    i += 1
    iterator(i, f)

iterator(i,f)

If you want iteration:

f = [0,1,2,3]
i = 0

def iterator(i, f):
    print i
    while i < len(f):
        print i
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        i += 1
    return f

iterator(i,f)

Or, if you declare these variables as global, so that changes persist:

f = [0,1,2,3]
i = 0

def iterator():
    global i
    global f
    print i
    while i < len(f):
        print i
        if i == 0:
            print "Restarted."
        else:
            print "iterate."
        function()
    return f

def function():
    global i
    i += 1
    iterator()

iterator()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.