0

is there a method where I can change the stepsize of a for loop, while I am in the loop?

E.g. this doesn't seem to work:

stepsize = 1.
for i in np.arange(0.,10.,stepsize):
   print i
   if i > 5:
      print "change"
      stepsize = 0.5

Output:

0.0
1.0
2.0
3.0
4.0
5.0
6.0
change
7.0
change
8.0
change
9.0
change
abenci
  • 8,422
  • 19
  • 69
  • 134
Leschge
  • 146
  • 3
  • 11
  • 3
    I don't think you can do that, but you can using a while loop, https://stackoverflow.com/questions/46179757/changing-step-in-python-loop/46180145 – Devesh Kumar Singh Jan 12 '20 at 14:53
  • 2
    No, since the range object is constructed before entering the `for` loop. The parameters on which it depends can then change, since these are already "copied" into the object. – Willem Van Onsem Jan 12 '20 at 14:55
  • @WillemVanOnsem "copied" sounds misleading. Makes it sound like `np.arange` gives you some lightweight on-the-fly-computing object like `range` does, instead of a fully materialized array. I'd rather call it "used" than "copied". – Kelly Bundy Jan 12 '20 at 16:43
  • Replace your simple `arange` with something more complex, like `np.r_[0:6:1,6:10:.5]`, which concatenates 2 `aranges`. When working with `numpy` try not think iteratively. Think in terms of arrays, and ranges of values. – hpaulj Jan 12 '20 at 18:41
  • Would you be willing to use a custom range object? – MisterMiyagi Jan 13 '20 at 07:14

2 Answers2

3

The problem

The issue is that when you declare your for loop as such:

for i in np.arange(0.,10.,stepsize):

The range object gets evaluated before the loop starts executing, and then never gets modified. As an analogy, consider this integer example:

a = 0
b = a + 1 #b is evaluated using the *current* value of a, which is 0
a = 1
print(b) # 0 + 1 = 1

The same applies for this for loop declaration, as it is equivalent to:

myRange = np.arange(0.,10.,stepsize)
for i in myRange:
    #...
    stepsize = 0.5 #will NOT affect the already initialized myRange variable!!

A simple solution

Therefore, the easiest solution is, in my opinion, to simply use a while loop, and increment your counter according to your if condition:

i = 1.
while i < 10:
    print i
    if i > 5:
        print "change"
        i += 0.5 #condition met, increment by 0.5 instead of 1
    else:
        i += 1 #otherwise, increment by 1
Anis R.
  • 6,656
  • 2
  • 15
  • 37
1

Not with np.arange, as that gives you a fully materialized array:

>>> r = np.arange(0., 1000000., 1)
>>> type(r)
<class 'numpy.ndarray'>
>>> r.__sizeof__()
8000048

So after it is created, it's simply too late. Also, it has no access to your stepsize variable, so couldn't notice that you changed it.

Compare to range, which is a lightway object that calculates values on the fly:

>>> r = range(0, 1000000, 1)
>>> type(r)
<class 'range'>
>>> r.__sizeof__()
24

(Note I'm using Python 3... you should really join us. Not just because in Python 2 the equivalent has the ugly name xrange.)

Now... that range object does have a step attribute:

>>> dir(r)
['__bool__', '__class__', ..., 'count', 'index', 'start', 'step', 'stop']

But it won't let us change it:

>>> r.step = 2
Traceback (most recent call last):
  File "<pyshell#46>", line 1, in <module>
    r.step = 2
AttributeError: readonly attribute

You could use your own range class supporting such a state change, though:

class Range:
    def __init__(self, start, stop, step):
        self.current = start
        self.stop = stop
        self.step = step
    def __iter__(self):
        while self.current < self.stop:
            yield self.current
            self.current += self.step

for i in (r := Range(0, 10, 1)):
   print(i)
   if i > 5:
      print("change")
      r.step = 0.5

Output:

0
1
2
3
4
5
6
change
6.5
change
7.0
change
7.5
change
8.0
change
8.5
change
9.0
change
9.5
change
Kelly Bundy
  • 23,480
  • 7
  • 29
  • 65