I have a python for loop that iterates over a dictionary.
The dictionary is very large. For debugging I want to modify the for loop to run only once. How can I limit the for loop to exit after running once.
for key in dic:
do_some_stuff()
#after the first iteration exit
In java I can modify the for loop in this way:
for (int i = 0; i < n; i++)
doSomeStuff();
will be limited like this:
for (int i = 0; i < n, i < 1; i++)
doSomeStuff();
Just use break:
It terminates the nearest enclosing loop, skipping the optional else
clause if the loop has one.
for key in dic:
do_some_stuff()
break
If you want to run the loop for several times (more than once), you can use a counter:
for i, key in enumerate(dic):
do_some_stuff()
if i > 10:
break
This will run the loop for 11 times before breaking as index (i) begins from 0 and goes till 10 .
Since you are doing debugging, I would like to recommend you the pdb package:
With it, you got much more control over the execution process.
import pdb
pdb.set_trace()
for key in dic:
do_some_stuff()
You can always slice the dictionary iterator using
from itertools import islice
for k in islice(dic, 1):
do_something(k)
Then, to disable it, simply change the 1 to None. Thus you could have a debug flag that decides for you:
please_debug = True
for k in islice(dic, 1 if please_debug else None):
do_something(k)
Related
Let l be a list. In this python script, is the function len(l) executed every time in the for loop?
for i in range(len(l)):
#do something here
If so, it would be very wasteful when len(l) is large. We should introduce a=len(l) and then use range(a) in the for loop so that len function is only used once.
What about the following case?
for i in range(3+4):
#do something here
Is 3+4 computed every time or just once?
What about the for loop in C such as the following?
for (i = 1; i < 3+4; i++) {
do something here;
}
In Python, the for argument range(len(l)) is evaluated once and produces a range object that acts like a generator when it is requested for each iteration of the loop until it reaches the end of the sequence. Thus len(l) is only evaluated once. Note that depending on what you do in the loop body, a simpler for i in l: might be more appropriate.
In the C expression for (i = 1; i < 3+4; i++), the test clause i < 3+4 is evaluated before each iteration of the loop and since 3+4 is a constant expression, the compiler evaluates it at compile time and just generates code to compare i with 7 before the iteration of the loop.
Note however that if the loop is small, the compiler might expand it into a sequence of operations and remove the tests and increment completely.
I need to decrement in a python for-loop at a special case (or just don't increment).
In C-like languages, this can be easily accomplished by decrementing the index, or if you have an iterator-like structure you could just "decrement" the iterator. But I have no clue how to achieve this in python.
One solution would be to create a while loop and increment manually, but that would, in my case, bring in lots of extra cases, where just one case is needed when I could decrement.
C Example
for (int i = 0; i < N; ++i) {
if (some_condition) {
i--;
}
}
Python equivalent
for i in range(0, N):
if some_condition:
i -= 1 # need something like this
i = i.prev() # or like this
You can make an iteration loop by yourself. You could easily add a loop index independent from next calls, so that you could even use a skip condition that uses the current index.
skip_iteration = True
it = iter(range(10))
iterating = True
value = next(it)
while iterating:
try:
print(value, end=' ')
if skip_iteration:
# iteration skip code
skip_iteration = False
else:
# normal iteration code
value = next(it)
except StopIteration:
iterating = False
Here only the first iteration is skipped, which outputs :
0 0 1 2 3 4 5 6 7 8 9
This code relies on the fact that the next function raises StopIteration if it reaches the end of the iterator.
If the condition in the if statement is somehow related to the iterator i itself then the loop might not end but if the condition is not depended on i then there shall not be any problem
you can also try skipping that particular iteration by using continue.
In C, I would do this:
int i;
for (i = 0;; i++)
if (thereIsAReasonToBreak(i))
break;
How can I achieve something similar in Python?
Using itertools.count:
import itertools
for i in itertools.count(start=1):
if there_is_a_reason_to_break(i):
break
In Python 2, range() and xrange() were limited to sys.maxsize. In Python 3 range() can go much higher, though not to infinity:
import sys
for i in range(sys.maxsize**10): # you could go even higher if you really want
if there_is_a_reason_to_break(i):
break
So it's probably best to use count().
def to_infinity():
index = 0
while True:
yield index
index += 1
for i in to_infinity():
if i > 10:
break
Simplest and best:
i = 0
while not there_is_reason_to_break(i):
# some code here
i += 1
It may be tempting to choose the closest analogy to the C code possible in Python:
from itertools import count
for i in count():
if thereIsAReasonToBreak(i):
break
But beware, modifying i will not affect the flow of the loop as it would in C. Therefore, using a while loop is actually a more appropriate choice for porting that C code to Python.
Reiterating thg435's comment:
from itertools import takewhile, count
def thereIsAReasonToContinue(i):
return not thereIsAReasonToBreak(i)
for i in takewhile(thereIsAReasonToContinue, count()):
pass # or something else
Or perhaps more concisely:
from itertools import takewhile, count
for i in takewhile(lambda x : not thereIsAReasonToBreak(x), count()):
pass # or something else
takewhile imitates a "well-behaved" C for loop: you have a continuation condition, but you have a generator instead of an arbitrary expression. There are things you can do in a C for loop that are "badly behaved", such as modifying i in the loop body. It's possible to imitate those too using takewhile, if the generator is a closure over some local variable i that you then mess with. In a way, defining that closure makes it especially obvious that you're doing something potentially confusing with your control structure.
If you want to use a for loop, it's possible to combine built-in functions iter (see also this answer) and enumerate for an infinite for loop which has a counter. We're using iter to create an infinite iterator and enumerate provides the counting loop variable. The start value is zero by default, but you can set a different start value with the start argument.
for i, _ in enumerate(iter(bool, True), start=1):
input(i)
Which prints:
1
2
3
4
5
...
If you're doing that in C, then your judgement there is as cloudy as it would be in Python :-)
For a loop that exits on a simple condition check at the start of each iteration, it's more usual (and clearer, in my opinion) to just do that in the looping construct itself. In other words, something like (if you need i after loop end):
int i = 0;
while (! thereIsAReasonToBreak(i)) {
// do something
i++;
}
or (if i can be scoped to just the loop):
for (int i = 0; ! thereIsAReasonToBreak(i); ++i) {
// do something
}
That would translate to the Python equivalent:
i = 0
while not there_is_a_reason_to_break(i):
# do something
i += 1
Only if you need to exit in the middle of the loop somewhere (or if your condition is complex enough that it would render your looping statement far less readable) would you need to worry about breaking.
When your potential exit is a simple one at the start of the loop (as it appears to be here), it's usually better to encode the exit into the loop itself.
def infinity():
i=0
while True:
i+=1
yield i
for i in infinity():
if there_is_a_reason_to_break(i):
break
def natural_numbers():
yield from map(sum, enumerate(iter(int,1)))
for i in natural_numbers():
if there_is_a_reason_to_break(i):
break;
I would like to reproduce this JavaScript bucle in Python, but I'm not sure how to proceed.
for (i=0;i<toks.length && toks[i]!='\r'; i+=10)
you can write it using while
i = 0
while(i < len(toks) and toks[i] != '\r'):
i += 10
Personally, I don't like looping with indexes, so I suggest python for loop with break state (which stops the loop). Regarding every 10th loop logic, you can have this using toks[::10].
So the final code would be:
for v in toks[::10]:
if v == '\r':
break
print(v)
You can configure increment with 3rd argument of range and then check exit condition within the loop
toks = list(range(100))
toks[30] = '\r'
for i in range(0, len(toks), 10):
if toks[i] == '\r':
break
print(toks[i])
If you want to add 10 to the iterator variable for each loop, you can simply do so by passing it as a parameter to the range function:
for i in range(0, MAX_ITERATIONS, 10):
pass
The range function takes 3 parameters: the value to initialize the iterator variable, the maximum iterations and the value to add to the iterator variable for each loop (default is 1).
A code like that in python would be something like:
for i in range(0, len(toks), 10):
if toks[i] == '\r':
break
This code was written in Python 3.6 in Jupyter Notebooks. In other languages, I am pretty sure I built loops that looked like this:
endRw=5
lenDF=100 # 1160
for i in range(0, lenDF):
print("i: ", i)
endIndx = i + endRw
if endIndx > lenDF:
endIndx = lenDF
print("Range to use: ", i, ":", endIndx)
# this line is a mockup for an index that is built and used
# in the real code to do something to a pandas DF
i = endIndx
print("i at end of loop", i)
In testing though, i does not get reset to endIndx and so the loop does not build the intended index values.
I was able to solve this problem and get what I was looking for by building a while loop like this:
endRw=5
lenDF=97 # 1160
i = 0
while i < lenDF:
print("i: ", i)
endIndx = i + endRw
if endIndx > lenDF:
endIndx = lenDF
print("Range to use: ", i, ":", endIndx)
# this line is a mockup for an index that is built and used
# in the real code to do something to a pandas DF
i = endIndx
print("i at end of loop: ", i)
Question: is there a way to modify the i from inside the for loop in python? Is there a way to do what I did with the while loop using a for loop in Python?
Solved the problem with while but just curious about this.
You can modify the loop variable in a for loop, the problem is that for loops in Python are not like "old-style" for loops in e.g. Java, but more like "new-style" for-each loops.
In Python, for i in range(0, 10): does not behave like for (int i = 0; i < 10; i++) {, but like for (int i : new int[] {0, 1, ..., 10}}.
That is, in each iteration of the loop, the loop head will not modify the loop variable (e.g. increment it), but assign a new value to it, i.e. the next value from the given iterable (a range in your case). Thus, any modification that you did in the previous iteration are overwritten.
If you want to loop a known number of iterations or for every item in an iterable, use a for loop, but if you want to loop until a certain condition (no longer) holds, as in your case, use while.
for loops operate on iterables. In for i in range(0, lenDF), i is assigned the next value in the range on each round of the loop regardless of how it is used in the loop. The question then, is whether there is a clean way to write an iterable that does what you want. In this case, all you want is to advance by a fixed step and adjust the final step length to account for the end of data.
endRw=5
lenDF=97 # 1160
for i in range(0, lenDF, endRw):
endIndx = min(i+endRw, lenDF)
print("Range to use: ", i, ":", endIndx)
This answer is unlikely to be useful, but since you were just curious:
The closest I think to what you want to do would be using a generator and its send method:
>>> def jumpable_range(start, stop):
... i = start
... while i <= stop:
... j = yield i
... i = i + 1 if j is None else j
...
>>> R = jumpable_range(2, 10)
>>>
>>> for i in R:
... if i==5:
... i = R.send(8)
... print(i)
...
2
3
4
8
9
10
>>>
Taking the original question literally:
#Tobias_k provides a good explanation of when to use while versus for loops, and the use case of this question fits while better (at least for Python). In short: you cannot directly modify the i in for i in because of how this code works under the covers in Python. So while should be used for a use case where you need to change your counter inside a loop (in Python).
#tdelaney provides a good answer in terms of refactoring the code using a Python for loop given the way Python behaves (the accepted answer to this question).
#PaulPanzer provides concepts that, while over-complicated, are useful to students to explore new concepts; but the answer solves the for loop problem by using a while loop inside an iterator and calling that into the for loop.
Even so, the concepts explored that play to the use of yield and iterators are worth exploring. If we take these concepts and attempt to re-write the original code to exploit them, this is what that code would look like:
def jumpable_range(start, stop):
i = start
while i <= stop:
j = yield i
i = i + 1 if j is None else j
endRw=5
lenDF=97 # 1160
Q = jumpable_range(0,lenDF)
for i in Q:
print("i: ", i)
endIndx = i + endRw
if endIndx > lenDF:
endIndx = lenDF
if i == endIndx: break
print("Range to use: ", i, ":", endIndx)
# this line is a mockup for an index that is built and used
# in the real code to do something to a pandas DF
i = Q.send(endIndx-1)
print("i at end of loop", i)
You always can set the value of i in a for loop. The problem is your setting value statement is before the implicit for loop setting value statement and covered by latter. You cannot change the rule to make latter statement useless. You shouldn't do this even you can. Just change to use proper conditional variable.