Генераторы лениво оценивают так return
или yield
будут вести себя по-другому, когда вы отлаживаете код или выдается исключение.
За return
исключением любых случаев, когда вы generator
ничего не знаете generate_all
, это происходит потому, что когда generator
вы действительно выполняете, вы уже вышли из generate_all
функции. С yield
там это будет generate_all
в трассировке.
def generator(some_list):
for i in some_list:
raise Exception('exception happened :-)')
yield i
def generate_all():
some_list = [1,2,3]
return generator(some_list)
for item in generate_all():
...
Exception Traceback (most recent call last)
<ipython-input-3-b19085eab3e1> in <module>
8 return generator(some_list)
9
---> 10 for item in generate_all():
11 ...
<ipython-input-3-b19085eab3e1> in generator(some_list)
1 def generator(some_list):
2 for i in some_list:
----> 3 raise Exception('exception happened :-)')
4 yield i
5
Exception: exception happened :-)
И если он использует yield from
:
def generate_all():
some_list = [1,2,3]
yield from generator(some_list)
for item in generate_all():
...
Exception Traceback (most recent call last)
<ipython-input-4-be322887df35> in <module>
8 yield from generator(some_list)
9
---> 10 for item in generate_all():
11 ...
<ipython-input-4-be322887df35> in generate_all()
6 def generate_all():
7 some_list = [1,2,3]
----> 8 yield from generator(some_list)
9
10 for item in generate_all():
<ipython-input-4-be322887df35> in generator(some_list)
1 def generator(some_list):
2 for i in some_list:
----> 3 raise Exception('exception happened :-)')
4 yield i
5
Exception: exception happened :-)
Однако это происходит за счет производительности. Дополнительный уровень генератора имеет некоторые накладные расходы. Так return
что, как правило, будет немного быстрее, чем yield from ...
(или for item in ...: yield item
). В большинстве случаев это не имеет большого значения, потому что все, что вы делаете в генераторе, обычно доминирует во время выполнения, так что дополнительный слой не будет заметен.
Тем yield
не менее, имеет ряд дополнительных преимуществ: вы не ограничены одной итерацией, вы также можете легко получить дополнительные элементы:
def generator(some_list):
for i in some_list:
yield i
def generate_all():
some_list = [1,2,3]
yield 'start'
yield from generator(some_list)
yield 'end'
for item in generate_all():
print(item)
start
1
2
3
end
В вашем случае операции довольно просты, и я не знаю, нужно ли вообще создавать несколько функций для этого, map
вместо этого можно было бы просто использовать встроенное выражение или выражение генератора:
map(do_something, get_the_list()) # map
(do_something(i) for i in get_the_list()) # generator expression
Оба должны быть идентичны (за исключением некоторых различий, когда случаются исключения) для использования. И если им нужно более описательное имя, вы все равно можете заключить их в одну функцию.
Есть несколько помощников, которые обертывают очень распространенные операции для встроенных итераторов, а дополнительные можно найти во встроенном itertools
модуле. В таких простых случаях я бы просто прибегнул к этим и только для нетривиальных случаев написал свои собственные генераторы.
Но я предполагаю, что ваш реальный код более сложный, поэтому он может быть неприменим, но я подумал, что это не будет полным ответом без упоминания альтернатив.