使用python函数返回生成器或普通对象的问题

ziy*_*ang 6 python iteration yield generator

我将函数定义f

def f(flag):
    n = 10
    if flag:
        for i in range(n):
            yield i
    else:
        return range(n)
Run Code Online (Sandbox Code Playgroud)

f无论如何返回发电机flag:

>>> f(True)
<generator object f at 0x0000000003C5EEA0>

>>> f(False)
<generator object f at 0x0000000007AC4828>
Run Code Online (Sandbox Code Playgroud)

如果我遍历返回的对象:

# prints normally
for i in f(True):
    print(i)

# doesn't print
for i in f(False):
    print(i)
Run Code Online (Sandbox Code Playgroud)

看起来像f(False)返回已经迭代的生成器.什么原因?谢谢.

Mar*_*ers 12

包含yield语句的函数始终返回生成器对象.

只有当您遍历该生成器对象时,才会执行该函数中的代码.在那之前,函数中没有代码被执行,Python 也不知道你只会返回.

注意,return在生成器函数中使用具有常规函数不同的语义 ; return在这种情况下,简单地被视为"退出发电机"; 返回值被丢弃,因为生成器只能通过yield表达式生成值.

看起来你想要使用yield from:

def f(flag):
    n = 10
    if flag:
        for i in range(n):
            yield i
    else:
        yield from range(n)
Run Code Online (Sandbox Code Playgroud)

yield from 需要Python 3.3或更高版本.

请参阅yield表达式文档:

yield在函数体中使用表达式会使该函数成为生成器.

当调用生成器函数时,它返回一个称为生成器的迭代器.然后该生成器控制生成器函数的执行.当调用其中一个生成器的方法时,执行开始.那时,执行继续执行第一个yield表达式,再次暂停它,将expression_list的值返回给生成器的调用者.

对生成器的迭代调用generator.__next__()方法,触发执行.

如果您想在某些时候返回生成器,则不要yield在此函数中使用.你可以通过其他方式生产发电机; 例如,使用单独的函数,或者使用生成器表达式:

def f(flag):
    n = 10
    if flag:
        return (i for i in range(n))
    else:
        return range(n)
Run Code Online (Sandbox Code Playgroud)

现在没有yield使用f它,它将不再直接生成生成器对象.相反,生成器表达式(i for i in range(n))会生成它,但只能有条件地生成它.


dan*_*ano 5

您可以使用实际使用的嵌套函数来解决此问题yield

def f(flag):
    def gen():
        for i in range(n):
            yield i
    n = 10
    if flag:
        return gen()
    else:
        return range(n)

>>> f(True)
<generator object gen at 0x7f62017e3730>
>>> f(False)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Run Code Online (Sandbox Code Playgroud)

正如Martijn指出的那样,任何包含a的函数yield都将始终返回一个生成器对象,因此,如果在某些情况下,您希望在调用主体时f实际执行该主体f(),而不是仅在迭代时才执行,则必须使用此方法。

标准库实例方法mapconcurrent.Futures.ProcessPoolExecutor/ concurrent.Futures.ThreadPoolExecutor使用它来确保期货提交,一旦map被调用,而不是只有当你尝试实际从中检索的结果,例如:

def map(self, fn, *iterables, timeout=None):
    if timeout is not None:
        end_time = timeout + time.time()

    fs = [self.submit(fn, *args) for args in zip(*iterables)]

    # Yield must be hidden in closure so that the futures are submitted
    # before the first iterator value is required.
    def result_iterator():
        try:
            for future in fs:
                if timeout is None:
                    yield future.result()
                else:
                    yield future.result(end_time - time.time())
        finally:
            for future in fs:
                future.cancel()
    return result_iterator()
Run Code Online (Sandbox Code Playgroud)