Closed. This question needs details or clarity。它当前不接受答案。
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            想改善这个问题吗?添加详细信息并通过editing this post阐明问题。
                        
                        5年前关闭。
                                                                                            
                
        
据我所知,监视异常会使程序变慢。

迭代器异常监视器(例如StopIteration)会使for循环变慢吗?

最佳答案

尽管在通常情况下,异常监视的开销很小,但是在迭代器的情况下,处理StopIteration异常似乎没有任何开销。 Python将迭代器作为一种特殊情况进行优化,以使StopIteration不涉及任何异常处理程序。 (我还将观察-可能遗漏了一些东西-很难提出没有隐式使用迭代器的Python for循环)。

下面是一些示例,首先使用内置的range函数和简单的for循环:

Python 2.7.5
>>> import dis
>>> def x():
...   for i in range(1,11):
...     pass
...
>>> dis.dis(x)
  2           0 SETUP_LOOP              23 (to 26)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (1)
              9 LOAD_CONST               2 (11)
             12 CALL_FUNCTION            2
             15 GET_ITER
        >>   16 FOR_ITER                 6 (to 25)
             19 STORE_FAST               0 (i)

  3          22 JUMP_ABSOLUTE           16
        >>   25 POP_BLOCK
        >>   26 LOAD_CONST               0 (None)
             29 RETURN_VALUE


请注意,范围实际上被视为迭代器。

现在,使用一个简单的生成器函数:

>>> def g(x):
...   while x < 11:
...     yield x
...     x = x + 1
...
>>> def y():
...   for i in g(1):
...     pass
...
>>> dis.dis(y)
  2           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (g)
              6 LOAD_CONST               1 (1)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

  3          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE
>>> dis.dis(g)
  2           0 SETUP_LOOP              31 (to 34)
        >>    3 LOAD_FAST                0 (x)
              6 LOAD_CONST               1 (11)
              9 COMPARE_OP               0 (<)
             12 POP_JUMP_IF_FALSE       33

  3          15 LOAD_FAST                0 (x)
             18 YIELD_VALUE
             19 POP_TOP

  4          20 LOAD_FAST                0 (x)
             23 LOAD_CONST               2 (1)
             26 BINARY_ADD
             27 STORE_FAST               0 (x)
             30 JUMP_ABSOLUTE            3
        >>   33 POP_BLOCK
        >>   34 LOAD_CONST               0 (None)
             37 RETURN_VALUE


请注意,这里的y与上面的x基本上相同,区别在于一条LOAD_CONST指令,因为x引用数字11。同样,我们的简单生成器基本上等效于一段时间内编写的相同内容。环:

>>> def q():
...   x = 1
...   while x < 11:
...     x = x + 1
...
>>> dis.dis(q)
  2           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (x)

  3           6 SETUP_LOOP              26 (to 35)
        >>    9 LOAD_FAST                0 (x)
             12 LOAD_CONST               2 (11)
             15 COMPARE_OP               0 (<)
             18 POP_JUMP_IF_FALSE       34

  4          21 LOAD_FAST                0 (x)
             24 LOAD_CONST               1 (1)
             27 BINARY_ADD
             28 STORE_FAST               0 (x)
             31 JUMP_ABSOLUTE            9
        >>   34 POP_BLOCK
        >>   35 LOAD_CONST               0 (None)
             38 RETURN_VALUE


同样,没有特定的开销来处理迭代器或生成器(range可能比生成器版本有所优化,只是因为它是内置的,而不是由于Python处理它的方式)。

最后,让我们看一下用StopIteration编写的实际显式迭代器

>>> class G(object):
...   def __init__(self, x):
...     self.x = x
...   def __iter__(self):
...     return self
...   def next(self):
...     x = self.x
...     if x >= 11:
...       raise StopIteration
...     x = x + 1
...     return x - 1
...
>>> dis.dis(G.next)
  7           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (x)
              6 STORE_FAST               1 (x)

  8           9 LOAD_FAST                1 (x)
             12 LOAD_CONST               1 (11)
             15 COMPARE_OP               5 (>=)
             18 POP_JUMP_IF_FALSE       30

  9          21 LOAD_GLOBAL              1 (StopIteration)
             24 RAISE_VARARGS            1
             27 JUMP_FORWARD             0 (to 30)

 10     >>   30 LOAD_FAST                1 (x)
             33 LOAD_CONST               2 (1)
             36 BINARY_ADD
             37 STORE_FAST               1 (x)

 11          40 LOAD_FAST                1 (x)
             43 LOAD_CONST               2 (1)
             46 BINARY_SUBTRACT
             47 RETURN_VALUE


现在,在这里我们可以看到,生成器函数所涉及的指令比该简单迭代器要少一些,主要与实现上的差异有关,而与引起StopIteration异常有关的几条指令有关。但是,使用此迭代器的函数与上面的y完全等效:

>>> def z():
...   for i in G(1):
...     pass
...
>>> dis.dis(z)
  2           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (G)
              6 LOAD_CONST               1 (1)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

  3          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE


当然,这些结果基于以下事实:Python for循环将优化迭代器,以消除对StopIteration异常的显式处理程序的需要。毕竟,StopIteration异常本质上是Python for循环操作的正常部分。



关于为什么以这种方式实现,请参见PEP-234,它定义了迭代器。这专门解决了异常消耗的问题:


  
  有人质疑是否有异常信号表明
    迭代并不太昂贵。几种替代方法
    已提出StopIteration异常:特殊值End
    发出结束信号,函数end()测试迭代器是否
    完成,甚至重用IndexError异常。
  
  
  一个特殊值的问题是,如果序列
  包含该特殊值,该序列上的循环将
  提前结束而没有任何警告。如果有经验
  以null结尾的C字符串并没有教会我们这个问题
  会引起想象的Python自省工具麻烦
  将遍历所有内置名称的列表,
  假设特殊的End值是内置名称!
  调用end()函数将需要每个调用两次
  迭代。两次通话比一次通话要贵得多
  加上异常测试。尤其是时间紧迫的
  for循环可以非常便宜地测试异常。
  重用IndexError可能会引起混乱,因为它可能是
  真正的错误,可以通过结束循环来掩盖
  过早地。

10-05 18:38