【问题标题】:Issue with a python function returning a generator or a normal objectpython函数返回生成器或普通对象的问题
【发布时间】:2014-08-14 16:41:43
【问题描述】:

我将函数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) 返回了一个已经被迭代的生成器。什么原因?谢谢。

【问题讨论】:

  • 如果一个函数中有一个yield,即使无法访问,它也总是一个生成器。

标签: python iteration generator yield


【解决方案1】:

包含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)

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

请参阅yield expression 文档:

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

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

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

如果您希望在某些时候返回一个生成器,那么不要在这个函数中使用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)) 会生成它,但只是有条件的。

【讨论】:

    【解决方案2】:

    您可以通过使用实际使用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 使用它来确保在调用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()
    

    【讨论】:

      猜你喜欢
      • 2020-04-23
      • 2015-09-19
      • 1970-01-01
      • 2015-03-26
      • 2020-03-14
      • 1970-01-01
      • 1970-01-01
      • 2016-04-14
      • 1970-01-01
      相关资源
      最近更新 更多