我写了这段简单的代码:
def mymap(func, *seq):
return (func(*args) for args in zip(*seq))
我应该使用上述“return”语句返回生成器,还是使用“yield from”指令,如下所示:
def mymap(func, *seq):
yield from (func(*args) for args in zip(*seq))
除了“ yield ”与“ yield ”之间的技术差异之外,通常情况下哪种方法更好?
最佳答案
不同之处在于您的第一个mymap
只是一个常用函数,
在这种情况下,工厂将退还发电机。一切
调用该函数后,体内的对象将立即执行。
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
也是一个工厂,但是它也是一个生成器本身,来自内部的自建子发电机。
因为它本身就是生成器,所以执行 body
直到next(generator)的第一次调用才开始。
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)
我认为以下示例将使其更加清晰。
我们定义了要使用功能处理的数据包,
bundle 在工作,我们传递给发电机。
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这样的交互式shell中运行以下代码,以
看到不同的行为。
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
一样,我们将其扩展一点并制作一个协程通过将 yield 分配给变量来解决这个问题,所以我们可以注入(inject)工作
使用
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
之类的东西只有在您需要为要创建的多个生成器完成相同的事情时才有意义,或者在您的生成器构建过程非常复杂以至于证明使用工厂而不是用生成器在原地构建单个生成器时才有意义。理解。笔记:
上面关于
gen_generator
函数(第二个mymap
)状态的描述“它本身就是一个发电机”。这有点含糊,从技术上讲不是
确实正确,但有助于对功能差异进行推理
在这个棘手的设置中,
gen_factory
也返回一个生成器,即其中一台由发电机理解而建。
实际上任何函数(不仅是这个问题的函数,里面有生成器理解!),里面有
yield
,在调用时,返回一个生成器对象,该对象从函数主体中构造出来。
type(gen_coroutine) # function
gen_co = gen_coroutine(); type(gen_co) # generator
因此,我们在上面观察到的
gen_generator
和gen_coroutine
的整个操作发生在这些生成器对象中,之前带有
yield
的函数已经吐出。关于python - Python `yield from`,还是返回一个生成器?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41136410/