Большую часть времени вы всегда должны использовать цикл for
.Это несколько вещей для вас, что вы, вероятно, не хотите обрабатывать себя:
- Работает для итераторов и итераторов
- Обрабатывает
StopIteration
для вас (в CPython StopIteration
это дескрипторы в C вместо Python, что делает его значительно быстрее)
Это означает, что у вас есть более общий код и более быстрый код с for
.Так что это всегда должен быть предпочтительный вариант.
Однако в некоторых случаях вы на самом деле не можете использовать for
, тогда цикл while действительно хороший выбор.Чтобы сделать его более общим, вы также должны использовать iter
в качестве аргумента, чтобы вы также могли обрабатывать итерации, которые не являются итераторами:
_gen = iter(gen())
...
Следующий вопрос, который вам нужно задать себе: Вам нужнообрабатывать StopIteration
для каждого next
вызова или не имеет значения, где происходит StopIteration
?Ввод try
не требует больших накладных расходов (это относится только к try
- если он должен идти в except
, else
или finally
, там значительно больше накладных расходов), но это все еще накладные расходы - этопочему твой второй пример быстрее третьего.Так что, если не имеет значения, откуда взялся StopIteration
, то обтекание while True
в try
будет более быстрым вариантом:
try:
while True:
next(_gen)
except StopIteration:
pass
Есть несколько вариантов сделать while
подходить быстрее.Можно было бы избежать поиска глобального имени для next
, который происходит один раз для каждой итерации.
При использовании локальной переменной эта стоимость поиска происходит только один раз, и внутри цикла поиск локального имени выполняется намного быстрее:
def f(gen):
_gen = iter(gen())
_next = next
try:
while True:
x = _next(_gen)
except StopIteration:
return
Это был бы мой любимый подход, если бы мне пришлось использовать циклический подход while
.
Вы могли бы даже пойти на шаг дальше и избежать поиска __next__
, который происходит каждый разВы звоните next
.Однако это то, что (в некоторых случаях) будет отличаться от чистого поведения next
и должно выполняться только , если вы знаете, что делаете , и только если вам действительно нужна очень маленькая производительностьBoost это дает вам.В общем, вы НЕ должны использовать это :
def f(gen):
_gen = iter(gen())
_next = _gen.__next__
try:
while True:
x = _next()
except StopIteration:
return
Однако я не рекомендую этот подход, вы найдете некоторые действительно странные и странные проблемы с этим подходом (он может привести к разным результатам).И в действительности нельзя вызывать функции двойного подчеркивания напрямую.Я просто упомянул это для полноты.
Я также сделал тест для отображения производительности этих подходов:
from simple_benchmark import BenchmarkBuilder
b = BenchmarkBuilder()
@b.add_function()
def for_loop(gen):
for i in gen:
pass
@b.add_function()
def while_outer_try(gen):
_gen = iter(gen)
try:
while True:
x = next(_gen)
except StopIteration:
pass
@b.add_function()
def while_inner_try(gen):
_gen = iter(gen)
while True:
try:
x = next(_gen)
except StopIteration:
break
@b.add_function()
def while_outer_try_cache_next(gen):
_gen = iter(gen)
_next = next
try:
while True:
x = _next(_gen)
except StopIteration:
return
@b.add_function()
def while_outer_try_cache_next_method(gen):
_gen = iter(gen)
_next = _gen.__next__
try:
while True:
x = _next()
except StopIteration:
return
@b.add_arguments('length')
def argument_provider():
for exp in range(2, 20):
size = 2**exp
yield size, range(size)
r = b.run()
r.plot()
Резюме:
- Используйте циклический подход
for
, когда это возможно и возможно. - При использовании подхода
while
убедитесь, что вы используете iter
на повторяемом.Если вы хотите уменьшить производительность, поместите try
и except
за пределы while
(если это возможно) и кэшируйте поиск next
(не кэшируйте поиск __next__
, кроме тех случаев, когда вы действительно знаетечто вы принесете на себя, и вам нужно выжать еще больше производительности). - Подход
while
всегда будет медленнее, чем for
(по крайней мере, в CPython) и потребует значительно больше кода.Повторюсь: используйте его только если это действительно необходимо.