Python`yield from`还是返回一个生成器? [英] Python `yield from`, or return a generator?

查看:81
本文介绍了Python`yield from`还是返回一个生成器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我写了这段简单的代码:

I wrote this simple piece of code:

def mymap(func, *seq):
  return (func(*args) for args in zip(*seq))

我应该使用上面的'return'语句来返回生成器,还是使用这样的'yield from'指令:

Should I use the 'return' statement as above to return a generator, or use a 'yield from' instruction like this:

def mymap(func, *seq):
  yield from (func(*args) for args in zip(*seq))

除了回报"和收益率"之间的技术区别之外,通常情况下哪种方法更好?

and beyond the technical difference between 'return' and 'yield from', which is the better approach the in general case?

推荐答案

区别在于您的第一个mymap只是一个常用函数, 在这种情况下,工厂将退还发电机.一切 调用该函数后,体内的对象就会被执行.

The difference is that your first mymap is just a usual function, in this case a factory which returns a generator. Everything inside the body gets executed as soon as you call the function.

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也是一个工厂,但它也是一个生成器 本身,来自内部的自建子发电机. 因为它本身就是生成器,所以执行身体 直到第一次调用next(generator)才开始.

The second mymap is also a factory, but it's also a generator itself, yielding from a self-built sub-generator inside. Because it is a generator itself, execution of the body does not start until the first invokation of 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)

我认为以下示例将使其更加清晰. 我们定义了要使用功能处理的数据包, 捆绑在工作中,然后传递给生成器.

I think the following example will make it clearer. We define data packages which shall be processed with functions, bundled up in jobs we pass to the generators.

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()之后这样做.

Now we run the following code inside an interactive shell like IPython to see the different behavior. gen_factory immediately prints out, while gen_generator only does so after next() being called.

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()进入运行的发电机.

To give you a more reasonable use case example for a construct like gen_generator we'll extend it a little and make a coroutine out of it by assigning yield to variables, so we can inject jobs into the running generator with send().

此外,我们创建了一个辅助函数,它将运行所有任务 内的工作,并在完成后要求新的工作.

Additionally we create a helper function which will run all tasks inside a job and ask as for a new one upon completion.

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

现在,我们使用帮助函数do_job和两个作业来运行gen_coroutine.

Now we run gen_coroutinewith our helper function do_joband two jobs.

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之类的东西只有在您需要为要创建的多个发电机完成相同的工作时才有意义,或者在您的发电机构造过程非常复杂以至于需要使用工厂而不是在现场建造单个发电机的情况下,IMO才有意义.生成器理解.

To come back to your question which version is the better approach in general. IMO something like gen_factory makes only sense if you need the same thing done for multiple generators you are going to create, or in cases your construction process for generators is complicated enough to justify use of a factory instead of building individual generators in place with a generator comprehension.

上面关于gen_generator功能(第二个mymap)状态的描述 它本身是生成器".这有点含糊,从技术上讲不是 确实正确,但有助于对功能差异进行推理 在这个棘手的设置中,gen_factory还会返回一个生成器,即 一种由发电机内部理解构建的.

The description above for the gen_generator function (second mymap) states "it is a generator itself". That is a bit vague and technically not really correct, but facilitates reasoning about the differences of the functions in this tricky setup where gen_factory also returns a generator, namely that one built by the generator comprehension inside.

实际上 any 函数(不仅是来自此问题的函数,内部具有生成器理解!),在调用时内部还有yield 返回一个生成器对象,该对象从函数主体中构造出来.

In fact any function (not only those from this question with generator comprehensions inside!) with a yield inside, upon invocation, just returns a generator object which gets constructed out of the function body.

type(gen_coroutine) # function
gen_co = gen_coroutine(); type(gen_co) # generator

所以我们在上面观察的gen_generatorgen_coroutine的整个动作 发生在这些生成器对象中,内部带有yield的函数之前已经吐出.

So the whole action we observed above for gen_generator and gen_coroutine takes place within these generator objects, functions with yield inside have spit out before.

这篇关于Python`yield from`还是返回一个生成器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆