python函数返回生成器或普通对象时出现问题

2eafrhcq  于 2023-01-19  发布在  Python
关注(0)|答案(3)|浏览(159)

我将函数f定义为

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

但是,无论flag是什么,f都返回一个生成器:

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

>>> f(False)
<generator object f at 0x0000000007AC4828>

如果我迭代返回的对象:

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

# doesn't print
for i in f(False):
    print(i)

看起来f(False)返回了一个生成器,它已经被迭代过了。原因是什么?谢谢。

pgvzfuti

pgvzfuti1#

包含yield语句的函数总是返回生成器对象。
只有当你迭代那个生成器对象时,函数中的代码才会被执行,在此之前,函数中的代码不会被执行,Python * 不知道 * 你会返回。
注意,在生成器函数中使用return与在常规函数中使用return具有 * 不同的语义 *;return在这种情况下被简单地看作“在此退出生成器”;由于生成器只能通过yield表达式产生值,因此丢弃返回值。
看起来您想改用yield from

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

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)

现在,f中没有使用yield,它也不再直接生成生成器对象,而是由生成器表达式(i for i in range(n))生成,但条件是有条件的。

qgelzfjb

qgelzfjb2#

您可以通过使用实际使用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]

正如Martijn所指出的,任何包含yield的函数都将始终返回一个生成器对象,因此如果在某些情况下您希望f的主体在调用f()时实际执行,而不是仅在迭代时执行,则必须使用这种方法。
concurrent.Futures.ProcessPoolExecutor/concurrent.Futures.ThreadPoolExecutor中的标准库示例方法map使用此方法来确保future在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()
y0u0uwnf

y0u0uwnf3#

生成器不会自动生成下一个yield值,除非您专门调用

next()

我有一个名为“generate parameters”的生成器,一旦我这样做了,它就能正常工作:

print(next(generate_parameters()))

相关问题