25

I have code like this:

loopcount = 3
for i in range(1, loopcount)
   somestring = '7'
   newcount = int(somestring)
   loopcount = newcount

so what I want is to modify the range of the for 'inside' the loop.

I wrote this code expecting the range of the for loop would change to (1,7) during the first loop, but it didn't happen.

Instead, no matter what number I put in, it only runs 2 times. (I want 6 times.. in this case)

I checked the value using print like this:

    loopcount = 3
    for i in range(1, loopcount)
       print loopcount
       somestring = '7'
       newcount = int(somestring)
       loopcount = newcount
       print loopcount
#output:
3
7
7
7

What is wrong? the number has been changed.

Where is my thinking wrong?

David Hall
  • 32,624
  • 10
  • 90
  • 127
H.Choi
  • 3,095
  • 7
  • 26
  • 24

8 Answers8

44

The range is created based on the value of loopcount at the time it is called--anything that happens to loopcount afterwards is irrelevant. What you probably want is a while statement:

loopcount = 3
i = 1
while i < loopcount:
    somestring = '7'
    loopcount = int(somestring)
    i += 1

The while tests that the condition i < loopcount is true, and if true, if runs the statements that it contains. In this case, on each pass through the loop, i is increased by 1. Since loopcount is set to 7 on the first time through, the loop will run six times, for i = 1,2,3,4,5 and 6.

Once the condition is false, when i = 7, the while loop ceases to run.

(I don't know what your actual use case is, but you may not need to assign newcount, so I removed that).

Justin Blank
  • 1,768
  • 1
  • 15
  • 32
  • 2
    The while should probably end with `i += 1` to simulate the automatic incrementing when iterating over a range – Joe Day Aug 10 '12 at 16:29
  • my for has many many codes inside it would it have no change in results if i use while and i+=1? – H.Choi Aug 10 '12 at 16:35
  • 1
    I'm not sure if I understand what you're asking. But the code inside of the loop will do the same thing regardless of whether it is a for or while loop. I also just explained more about the while loop in the answer. Does that help at all? – Justin Blank Aug 10 '12 at 16:37
  • 1
    oh it works! thank you very much! i've been wasting hours in this problem. thank you again. you really helped me a lot :) – H.Choi Aug 10 '12 at 16:45
  • Glad I could help you. It can take awhile before these things become second nature. – Justin Blank Aug 10 '12 at 16:50
11

From the range() docstring:

range([start,] stop[, step]) -> list of integers

Return a list containing an arithmetic progression of integers. range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0. When step is given, it specifies the increment (or decrement). For example, range(4) returns [0, 1, 2, 3]. The end point is omitted! These are exactly the valid indices for a list of 4 elements.

So, range(1, 10), for example, returns a list like: [1,2,3,4,5,6,7,8,9], so, your code is basically doing:

loopcount = 3
for i in [1, 2]:
    somestring = '7'
    newcount = int(somestring)
    loopcount = newcount

When your for loop is "initialized", the list is created by range().

Valdir Stumm Junior
  • 4,568
  • 1
  • 23
  • 31
5

The while-loop answer given by user802500 is likely to be the best solution to your actual problem; however, I think the question as asked has an interesting and instructive answer.

The result of the range() call is a list of consecutive values. The for-loop iterates over that list until it is exhausted.

Here is the key point: You are allowed to mutate the list during iteration.

>>> loopcount = 3
>>> r = range(1, loopcount)
>>> for i in r:
        somestring = '7'
        newcount = int(somestring)
        del r[newcount:]

A practical use of this feature is iterating over tasks in a todo list and allowing some tasks to generate new todos:

for task in tasklist:
    newtask = do(task)
    if newtask:
        tasklist.append(newtask)
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • Nice trick of using being mutable for list. So, I guess in the situation that the newcount is bigger than the original list size, instead of using 'del' to delete some elements from the range, we'd use 'append'. – ntough Jul 20 '15 at 14:08
2

When the range() function is evaluated in the for-loop it generates a sequence of values (ie a list) that will be used to iterate over.

range() uses the value of loopcount for this. However, once that sequence has been generated, nothing you do inside the loop will change that list, i.e., even if you change loopcount later, the original list will stay the same => the number of iterations will stay the same.

In your case:

loopcount = 3
for i in range(1, loopcount):

becomes

for i in [1, 2]:

So your loop iterates twice, since you have 2 print statements in the loop your get 4 lines of output. Note that you are printing the value of loopcount which is initially 3, but then gets set (and reset) to 7.

If you want to be able to change the iteration number dynamically consider using a while-loop instead. Of course you can always stop/exit any loop early with the use of the break statement.

Also,

   somestring = '7'
   newcount = int(somestring)

can be simplified to just

   newcount = 7
Levon
  • 138,105
  • 33
  • 200
  • 191
2

To specifically address the question "How do I change the range bounds", you can take advantage of the send method for a generator:

def adjustable_range(start, stop=None, step=None):
    if stop is None:
        start, stop = 0, start

    if step is None: step = 1

    i = start
    while i < stop:
        change_bound = (yield i)
        if change_bound is None:
            i += step
        else:
            stop = change_bound

Usage:

myrange = adjustable_range(10)

for i in myrange:
    if some_condition:
        myrange.send(20) #generator is now bounded at 20
Joel Cornett
  • 24,192
  • 9
  • 66
  • 88
  • 2
    +1, although this isn't a generator *expression* - just a generator. – lvc Aug 10 '12 at 16:42
  • You might want to try running your code. I tried to fix your version with another posted on this page. – Noctis Skytower Aug 10 '12 at 17:17
  • After comparing our answers, a question came to mind. Are you sure that `i += step` should not be executed when information is sent in through `yield`? My code executes the step unconditionally. – Noctis Skytower May 02 '16 at 21:17
1

It looks like your premise is that you have a default number of times the loop should execute but an occasional condition where it's different. It might be better to use a while loop instead, but regardless you can just do:

if i == some_calculated_threshold:
    break

to drop out of the loop instead.

moopet
  • 6,014
  • 1
  • 29
  • 36
  • 1
    -1; original question is trying to increase the number of times through the loop from 2 to 7; how is `break` supposed to help? – Wooble Aug 10 '12 at 17:43
0

You can't increase the number of iterations once the range has been set, but you can break out early, thereby decreasing the number of iterations:

for i in xrange(1, 7):
   if i == 2:
       break
kindall
  • 178,883
  • 35
  • 278
  • 309
0

Here is a more complete implementation of the adjustable_range function provided by Joel Cornett.

def adjustable_range(start, stop=None, step=None):
    if not isinstance(start, int):
        raise TypeError('start')
    if stop is None:
        start, stop = 0, start
    elif not isinstance(stop, int):
        raise TypeError('stop')
    direction = stop - start
    positive, negative = direction > 0, direction < 0
    if step is None:
        step = +1 if positive else -1
    else:
        if not isinstance(step, int):
            raise TypeError('step')
        if positive and step < 0 or negative and step > 0:
            raise ValueError('step')
    if direction:
        valid = (lambda a, b: a < b) if positive else (lambda a, b: a > b)
        while valid(start, stop):
            message = yield start
            if message is not None:
                if not isinstance(message, int):
                    raise ValueError('message')
                stop = message
            start += step
Noctis Skytower
  • 21,433
  • 16
  • 79
  • 117