我写了这段简单的代码:
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
主要适用于您只想迭代返回值然后操纵它们的情况。 return
主要适用于您希望存储函数在内存中生成的所有值而不是仅迭代它们一次的情况。请注意,您只能迭代一个生成器(产量返回)一次,有一些算法绝对不适合。
不同之处在于你的第一个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
也是一个工厂,但它也是一个发电机本身,由内部自建的子发电机产生。因为它是一个生成器本身,所以在下一次(生成器)的第一次调用之前,主体的执行才会开始。
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这样的交互式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
这样的构造提供一个更合理的用例示例,我们将稍微扩展它并通过为变量赋值来制作协程,因此我们可以使用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
and两个工作。
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
内部的函数之前已经吐出。
生成器使用yield
,函数使用return
。
生成器通常在for
循环中用于重复迭代由生成器自动提供的值,但也可以在另一个上下文中使用,例如。 G。在list()函数中创建列表 - 再次从生成器自动提供的值。
调用函数以提供返回值,每次调用只有一个值。