Python `for i in iter` vs` while True; i = next (iter) `
As I understand it, both of these approaches work for working on every element in the generator:
- let it
i
be our target operator - let it
my_iter
be our generator - let callable
do_something_with
returnNone
While Loop + StopIteratioon
try:
while True:
i = next(my_iter)
do_something_with(i)
except StopIteration:
pass
To understand the cycle / list
for i in my_iter:
do_something_with(i)
[do_something_with(i) for i in my_iter]
Minor change: print(i)
replaced by do_something_with(i)
as suggested by @kojiro to eliminate the use case using interpreter mechanics.
As far as I know, these are both applicable ways to iterate over a generator, is there any reason to prefer each other?
The for loop looks better than me right now. Because of: less lines / clutter and overall readability plus plus one padding.
I can really see that the while approach is an advantage if you want to easily break the loop into specific exceptions.
source to share
the third option is definitely NOT the same as the first two. the third example creates a list, one at a time for the return value print(i)
, which turns out to be None
therefore not a very interesting list.
the first two are semantically similar. There is a slight technical difference; the while loop, as shown, does not work unless my_iter
it is, in fact, an iterator (i.e., it has a method __next__()
); for example if it is a list
. The for loop works for all iterations (has a method __iter__()
) in addition to iterators.
Correct version:
my_iter = iter(my_iterable)
try:
while True:
i = next(my_iter)
print(i)
except StopIteration:
pass
Now, aside from readability considerations, there is actually a technical reason why you should prefer the for loop; there is a penalty you pay (in CPython, anyway) for the number of bytecodes executed in tight inner loops. let's compare:
In [1]: def forloop(my_iter):
...: for i in my_iter:
...: print(i)
...:
In [57]: dis.dis(forloop)
2 0 SETUP_LOOP 24 (to 27)
3 LOAD_FAST 0 (my_iter)
6 GET_ITER
>> 7 FOR_ITER 16 (to 26)
10 STORE_FAST 1 (i)
3 13 LOAD_GLOBAL 0 (print)
16 LOAD_FAST 1 (i)
19 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
22 POP_TOP
23 JUMP_ABSOLUTE 7
>> 26 POP_BLOCK
>> 27 LOAD_CONST 0 (None)
30 RETURN_VALUE
7 bytecodes called in vs inner loop:
In [55]: def whileloop(my_iterable):
....: my_iter = iter(my_iterable)
....: try:
....: while True:
....: i = next(my_iter)
....: print(i)
....: except StopIteration:
....: pass
....:
In [56]: dis.dis(whileloop)
2 0 LOAD_GLOBAL 0 (iter)
3 LOAD_FAST 0 (my_iterable)
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 STORE_FAST 1 (my_iter)
3 12 SETUP_EXCEPT 32 (to 47)
4 15 SETUP_LOOP 25 (to 43)
5 >> 18 LOAD_GLOBAL 1 (next)
21 LOAD_FAST 1 (my_iter)
24 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
27 STORE_FAST 2 (i)
6 30 LOAD_GLOBAL 2 (print)
33 LOAD_FAST 2 (i)
36 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
39 POP_TOP
40 JUMP_ABSOLUTE 18
>> 43 POP_BLOCK
44 JUMP_FORWARD 18 (to 65)
7 >> 47 DUP_TOP
48 LOAD_GLOBAL 3 (StopIteration)
51 COMPARE_OP 10 (exception match)
54 POP_JUMP_IF_FALSE 64
57 POP_TOP
58 POP_TOP
59 POP_TOP
8 60 POP_EXCEPT
61 JUMP_FORWARD 1 (to 65)
>> 64 END_FINALLY
>> 65 LOAD_CONST 0 (None)
68 RETURN_VALUE
9 Bytecodes in the inner loop.
However, we can do even better.
In [58]: from collections import deque
In [59]: def deqloop(my_iter):
....: deque(map(print, my_iter), 0)
....:
In [61]: dis.dis(deqloop)
2 0 LOAD_GLOBAL 0 (deque)
3 LOAD_GLOBAL 1 (map)
6 LOAD_GLOBAL 2 (print)
9 LOAD_FAST 0 (my_iter)
12 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
15 LOAD_CONST 1 (0)
18 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
21 POP_TOP
22 LOAD_CONST 0 (None)
25 RETURN_VALUE
everything happens in the C, collections.deque
, map
and print
- all built. (for cpython) so no bytecodes are executed for the loop in this case. This is only a useful optimization when the iteration step is a c-function (as is the case with print
. Otherwise, the overhead of calling a python function is greater than the overhead JUMP_ABSOLUTE
.
source to share
The for loop is the most pythonic. Note that you can exit the for loop as well as into loops.
Don't use a list comprehension unless you need the resulting list, otherwise you are unnecessarily storing all the elements. Your comprehension of the list of examples will only work with the print function in Python 3, it will not work with the print statement in Python 2.
source to share
I agree with you that the cycle is for
superior. As you mentioned, it's less messy and much easier to read. Programmers like to keep things as simple as possible, and the loop for
does just that. It's also best for beginner Python programmers who may not have learned try/except
. Also, as Alasdeir said, you can get out of the loop. Also, the loop while
triggers an error if you use a list if you don't use iter()
on first my_iter
.
source to share