python `for i in iter` vs `while True; i = next(iter)`



据我了解,这两种方法都适用于对生成器中的每个项目进行操作:

  • i成为我们的运营商目标
  • my_iter成为我们的发电机
  • 让可赎回do_something_with返回None

而循环+停止迭代

try:
    while True:
        i = next(my_iter)
        do_something_with(i)
except StopIteration:
    pass

对于循环/列表理解

for i in my_iter:
    do_something_with(i)

[do_something_with(i) for i in my_iter]

次要编辑:print(i)按照@kojiro的建议替换为do_something_with(i),以消除解释器机制用例的歧义。


据我所知,这两种都是迭代生成器的适用方法,有什么理由更喜欢其中一个吗?

现在,for 循环看起来对我来说更胜一筹。由于:更少的行/杂乱和一般的可读性,加上单个缩进。

我真的只认为,如果您想轻松打破特定异常的循环,while 方法才是优势。

第三个选项肯定与前两个选项不同。 第三个示例创建一个列表,每个列表用于返回值 print(i) ,恰好是None,所以不是一个非常有趣的列表。

前两者在语义上相似。 有一个微小的技术差异; 如前所述,如果my_iter不是迭代器(即,具有__next__()方法),则 while 循环不起作用;例如,如果它是一个list. for 循环适用于除迭代器之外的所有可迭代对象(具有__iter__()方法)。

因此,正确的版本是:

my_iter = iter(my_iterable)
try:
    while True:
        i = next(my_iter)
        print(i)
except StopIteration:
    pass

现在,除了可读性原因之外,实际上还有一个技术原因您应该更喜欢 for 循环; 您需要为在紧密内循环中执行的字节码数量支付(无论如何,在 CPython 中)支付的惩罚。 让我们比较一下:

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 个字节码 vs:

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 个字节码。

不过,我们实际上可以做得更好。

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

一切都发生在C中,collections.dequemapprint都是内置的。(对于 cPython)所以在这种情况下,没有为循环执行字节码。 仅当迭代步骤是 c 函数时,这才是一个有用的优化(就像 print 一样。 否则,python 函数调用的开销大于JUMP_ABSOLUTE开销。

for 循环是最 pythonic 的。请注意,您可以中断 for 循环以及 while 循环。

除非需要结果列表,否则不要使用列表推导,否则您将不必要地存储所有元素。您的示例列表推导仅适用于 Python 3 中的打印函数,不适用于 Python 2 中的打印语句。

我同意你的观点,for循环更胜一筹。正如你提到的,它不那么混乱,而且更容易阅读。程序员喜欢让事情尽可能简单,而for循环就是这样做的。对于可能没有学过try/except的新手 Python 程序员来说,这也更好。此外,正如阿拉斯代尔所提到的,你可以打破for循环。此外,如果您使用的是列表,则while循环会运行错误,除非您先对my_iter使用 iter()

最新更新