Должен ли я использовать оператор return, как указано выше, чтобы вернуть генератор, или использовать инструкцию yield from, например:
и помимо технической разницы между "доходностью" и "доходностью от", какой подход лучше в общем случае?
Ответ 2
Разница в том, что ваш первый mymap
- это обычная функция,
в этом случае a factory, который возвращает генератор. Все
внутри тела выполняется выполнение, как только вы вызываете функцию.
def gen_factory(func, seq):
"""Generator factory returning a generator."""
# do stuff ... immediately when factory gets called
print("build generator & return")
return (func(*args) for args in seq)
Второй mymap
также является factory, но он также является генератором
само собой, уступая из встроенного встроенного подгенератора внутри.
Поскольку это сам генератор, выполнение тела делает
не запускается до первого вызова следующего (генератора).
def gen_generator(func, seq):
"""Generator yielding from sub-generator inside."""
# do stuff ... first time when 'next' gets called
print("build generator & yield")
yield from (func(*args) for args in seq)
Я думаю, что следующий пример станет более понятным.
Мы определяем пакеты данных, которые должны обрабатываться с помощью функций,
объединенных в задания, которые мы передаем генераторам.
def add(a, b):
return a + b
def sqrt(a):
return a ** 0.5
data1 = [*zip(range(1, 5))] # [(1,), (2,), (3,), (4,)]
data2 = [(2, 1), (3, 1), (4, 1), (5, 1)]
job1 = (sqrt, data1)
job2 = (add, data2)
Теперь мы запускаем следующий код внутри интерактивной оболочки, такой как IPython, чтобы
см. различное поведение. gen_factory
сразу печатает
, а gen_generator
делает это только после вызова next()
.
gen_fac = gen_factory(*job1)
# build generator & return <-- printed immediately
next(gen_fac) # start
# Out: 1.0
[*gen_fac] # deplete rest of generator
# Out: [1.4142135623730951, 1.7320508075688772, 2.0]
gen_gen = gen_generator(*job1)
next(gen_gen) # start
# build generator & yield <-- printed with first next()
# Out: 1.0
[*gen_gen] # deplete rest of generator
# Out: [1.4142135623730951, 1.7320508075688772, 2.0]
Чтобы дать вам более разумный пример использования для конструкции
например gen_generator
, мы немного расширим его и сделаем сопрограмму
из этого, присвоив доходность переменным, чтобы мы могли вводить задания
в рабочий генератор с send()
.
Кроме того, мы создаем вспомогательную функцию, которая будет выполнять все задачи
внутри работы и спросить, как для нового по завершении.
def gen_coroutine():
"""Generator coroutine yielding from sub-generator inside."""
# do stuff... first time when 'next' gets called
print("receive job, build generator & yield, loop")
while True:
try:
func, seq = yield "send me work ... or I quit with next next()"
except TypeError:
return "no job left"
else:
yield from (func(*args) for args in seq)
def do_job(gen, job):
"""Run all tasks in job."""
print(gen.send(job))
while True:
result = next(gen)
print(result)
if result == "send me work ... or I quit with next next()":
break
Теперь мы запускаем gen_coroutine
с помощью нашей вспомогательной функции do_job
и двух заданий.
gen_co = gen_coroutine()
next(gen_co) # start
# receive job, build generator & yield, loop <-- printed with first next()
# Out:'send me work ... or I quit with next next()'
do_job(gen_co, job1) # prints out all results from job
# 1
# 1.4142135623730951
# 1.7320508075688772
# 2.0
# send me work... or I quit with next next()
do_job(gen_co, job2) # send another job into generator
# 3
# 4
# 5
# 6
# send me work... or I quit with next next()
next(gen_co)
# Traceback ...
# StopIteration: no job left
Чтобы вернуться к вашему вопросу, какая версия - лучший подход в целом.
IMO что-то вроде gen_factory
имеет смысл, если вам нужно то же самое для нескольких генераторов, которые вы собираетесь создавать, или в случаях, когда ваш процесс построения генераторов достаточно сложный, чтобы оправдать использование factory вместо того, чтобы создавать отдельные генераторы в место с пониманием генератора.
Примечание:
Описание выше для функции gen_generator
(второй mymap
)
"это сам генератор". Это немного расплывчато и технически не
действительно правильно, но облегчает рассуждение о различиях функций
в этой сложной настройке, где gen_factory
также возвращает генератор, а именно:
один из которых встроен в генераторное понимание внутри.
Фактически любая функция (а не только те из этого вопроса с встроенными генераторами внутри!) с yield
внутри, после вызова, просто
возвращает объект-генератор, который создается из тела функции.
type(gen_coroutine) # function
gen_co = gen_coroutine(); type(gen_co) # generator
Итак, все действие, которое мы наблюдали выше для gen_generator
и gen_coroutine
происходит внутри этих объектов генератора, функции с yield
внутри выплевывались раньше.