Подтвердить что ты не робот

Почему быстрее ломаться, чем возбуждать исключение?

После проверки нескольких простых тестов кажется, что быстрее может выйти из цикла, чтобы завершить генератор, а не для создания исключения StopIteration. Почему это так, если стандартный и принятый метод остановки генератора использует исключение. источник

In [1]: def f():
   ....:     for i in range(1024):
   ....:         yield None
   ....:         break
   ....:     

In [2]: def g():
   ....:     for i in range(1024):
   ....:         yield None
   ....:         raise StopIteration
   ....:     

In [3]: %timeit for i in f(): pass
1000000 loops, best of 3: 1.22 µs per loop

In [4]: %timeit for i in g(): pass
100000 loops, best of 3: 5.9 µs per loop

In [5]: %timeit for i in f(): pass
1000000 loops, best of 3: 1.22 µs per loop

In [6]: %timeit for i in g(): pass
100000 loops, best of 3: 5.82 µs per loop
4b9b3361

Ответ 1

Почему это так, если стандартный и принятый метод остановки генератора использует исключение.

Исключение StopIteration возникает только тогда, когда генератору больше ничего не нужно. И это не стандартный способ остановки генератора на полпути.

Вот два утверждения из документации генераторов о том, как их правильно остановить:

... предложение также устраняет путаницу в том, как прекратить      генератор: правильный путь return, а не raise StopIteration.

  1. PEP 255 - Простые генераторы

Q. Зачем разрешать "return" вообще? Почему не требуется принудительное завершение     "raise StopIteration"?

а. Механика StopIteration представляет собой детали низкого уровня, похожие на        механика IndexError в Python 2.1: реализация должна        сделать что-то хорошо определенное под обложками, а Python предоставляет        эти механизмы для продвинутых пользователей. Это не аргумент для        заставляя всех работать на этом уровне. "return" означает "Я        сделано" в любой функции, и это легко объяснить и использовать.        Обратите внимание, что "return" не всегда эквивалентно "raise StopIteration"       в try/except construct (см. "Спецификация: Возврат" )        раздел).

Таким образом, правильным способом было бы использовать оператор return вместо использования break или raise StopIteration.


кажется, что он может быть быстрее break из цикла для завершения генератора, а не для создания исключения StopIteration.

В самом деле, это связано с тем, что при создании исключения больше работы. Вы можете использовать модуль dis, чтобы посмотреть на байт-код:

In [37]: dis.dis(f)
  2           0 SETUP_LOOP              26 (to 29)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (1024)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                12 (to 28)
             16 STORE_FAST               0 (i)

  3          19 LOAD_CONST               0 (None)
             22 YIELD_VALUE         
             23 POP_TOP             

  4          24 BREAK_LOOP          
             25 JUMP_ABSOLUTE           13
        >>   28 POP_BLOCK           
        >>   29 LOAD_CONST               0 (None)
             32 RETURN_VALUE        

In [38]: dis.dis(g)
  2           0 SETUP_LOOP              31 (to 34)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (1024)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                17 (to 33)
             16 STORE_FAST               0 (i)

  3          19 LOAD_CONST               0 (None)
             22 YIELD_VALUE         
             23 POP_TOP             

  4          24 LOAD_GLOBAL              2 (StopIteration)
             27 RAISE_VARARGS            1
             30 JUMP_ABSOLUTE           13
        >>   33 POP_BLOCK           
        >>   34 LOAD_CONST               0 (None)
             37 RETURN_VALUE

Вы можете видеть, что почти все одинаково, но для повышения исключения оно должно выполнять дополнительные инструкции:

24 LOAD_GLOBAL              2 (StopIteration)
27 RAISE_VARARGS            1

Ответ 2

Если вы сделаете перерыв, тогда он просто выходит из цикла, и в вашем случае больше нечего делать, поэтому он также выходит из функции. Если вы создаете исключение, то он все равно должен обрабатывать все элементы исключений, которые происходят в фоновом режиме.

more work == more execution time

Причина StopIteration может быть использована (я никогда не использовал ее, поэтому не цитирую меня), чтобы поймать ее позже или просто выйти из вложенного цикла. Я просто быстро посмотрел на источник, который вы дали, и мне кажется, что StopIteration использовался только для иллюстрации работы генераторов, но он не использовался с генератором.

Надеюсь, что ответ на ваш вопрос.