【问题标题】:Getting the value of a return after a yield in python [duplicate]在python中获得收益后的回报值[重复]
【发布时间】:2022-01-08 11:05:10
【问题描述】:

我想知道在这样的函数中执行完所有yield之后如何获取函数的返回值:

def gen_test():
    l = []
    for i in range(6):
        l.append(i)
        yield i
    # i want to know this value after all iteration of yield
    return l

【问题讨论】:

  • 你能不能把它变成一个类方法,然后通过同一个类中的一个单独的getter方法收集那个值(如果你把它存储在self中)?
  • 同一函数中的 returnyield 在 py2 中返回错误 - 它在 py3 中是允许的,但适用于具有协同程序的特定用例。请参阅此问题以了解其背后的机制:stackoverflow.com/questions/26595895/…
  • 如果返回部分有效,则返回 l 并且我喜欢:return l, i
  • @match 我不会将此解释为 OP 特别想使用return 语句,而是他们想获得l 的值以某种方式 并且正在询问如何
  • 也许您一开始就不应该使用生成器?由于此生成器保留对生成的每个项目的引用,因此它并不比使用列表更有效。

标签: python yield


【解决方案1】:

简短的版本是不允许的。 在生成器中使用 return 语句传递值会导致 Python 3.3 版本之前的错误。对于这些版本的 Python,return 只能在没有表达式列表的情况下使用,等效于raise StopIteration

对于更高版本的Python,可以通过异常的value-attribute来提取返回值。

您可以在此处找到更多信息:Return and yield in the same function

【讨论】:

  • 这是错误的。这是完全允许的。
  • 如何允许?发帖人询问是否可以使用 return 语句传递表达式。
  • 看我的回答。这是允许的。正常的生成器函数返回None,因为很难获得返回值,因为for 掩盖了底层异常。这并不意味着它没有被使用。
  • 我的立场是正确的。更新了答案。
  • 谢谢。投票翻转。很好地提到了 3.3-
【解决方案2】:

好的,这个怎么样。不要从您的生成器中return,产生列表值l 作为您最后一次返回控制权。这样你就可以避免人们建议的所有 Python 卷积,然后继续你的一天。

您始终可以测试生成器的返回类型以查看其是否为列表。这在 Python 中被认为是完全有效的。

def gen_test():
    l = []
    for i in range(6):
        l.append(i)
        yield i
    yield l

for y in gen_test():
    if isinstance( y, type([])):
        print("List is ", y)
    else:
        print (y)
    

【讨论】:

    【解决方案3】:

    您不能轻易在生成器函数(包含yield 的函数)中使用return 来返回表达式,因为调用该函数会返回生成器。生成器中的return 与提高StopIteration 相同,如果您返回一个值,则会将其填充到 ecxeption 中。由于 Python 中的大多数迭代方式都不允许您访问 StopIteration,因此很难获得此值,除非您避开 for 和大多数其他迭代方式(例如 list())。

    您可以使用所需的返回值引发异常:

    class ReturnValue(Exception):
        pass
    
    def gen_test():
        l = []
        for i in range(6):
            l.append(i)
            yield i
        raise ReturnValue(l)
    
    try:
        for x in gen_test():
            print(x)
    except ReturnValue as e:
        print(e.args[0])  # or
        returnvalue = e.args[0]
        # note: variable e goes away after exception handler
    

    不过,这似乎是一个 hack。一个类可能会更好:

    class GenTest:
        value = None
        def __iter__(self):
            l = []
            for i in range(6):
                l.append(i)
                yield i
            self.value = l
    
    for x in (it := GenTest()):
        print(x)
    
    print(it.value)
    

    【讨论】:

    • 返回值初始化StopIteration。看我的回答
    • @MadPhysicist 这很好,但必须避开for 循环和其他迭代方法就像是砍断你的脚。坦率地说,我更喜欢课堂解决方案。
    • @MadPhysicist 是的,尽管我已经对他投了赞成票,因为他包含了一个基于类的解决方案(根据我对这个问题的原始评论),因为它具有可以迭代使用的优势普通方法 - 在本例中为 for 循环,但您可能还需要迭代许多其他方法,例如list(it) 并且必须手动捕获 StopIteration 可能不方便。
    • 根据我们的理解,前两句的措辞需要更改。我同意放弃for 循环是没有意义的,但事实仍然是returnyield 可以有意义地出现在同一个def 中。
    • 另外,你的类实现比我的好得多。
    【解决方案4】:

    迭代器函数的返回值用作它在终止时引发的StopIteration 的参数:

    >>> it = gen_test()
    >>> next(it)
    0
    >>> next(it)
    1
    >>> next(it)
    2
    >>> next(it)
    3
    >>> next(it)
    4
    >>> next(it)
    5
    >>> next(it)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration: [0, 1, 2, 3, 4, 5]
    

    只有第一个StopIteration 获得指定的值。进一步尝试迭代空生成器将引发空异常:

    >>> next(it)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    

    您可以通过避免for 循环并自己捕获异常来从迭代器的正常运行中提取返回值:

    it = gen_test()
    while True:
        try:
            x = next(it)
            # Do the stuff you would normally do in a for loop here
        except StopIteration as e:
            ret = e.value
            break
    print(ret)
    

    鉴于您必须在不使用 for 循环和无法返回累积的数据之间做出决定,所有这些都是一种尴尬的方法。假设您不同意尝试在外部累积数据(例如,只是做list(gen_test())),您可以使用类公开您的生成器状态:

    class gen_test:
        def __init__(self):
            self.l = []
            self.it = iter(range(6))
    
        def __iter__(self):
            return self
    
        def __next__(self):
            i = next(self.it)
            self.l.append(i)
            return i
    

    现在你可以做类似的事情

    >>> it = gen_test()
    >>> for x in it:
    ...     print(x)
    0
    1
    2
    3
    4
    5
    >>> print(it.l)
    [0, 1, 2, 3, 4, 5]
    

    【讨论】:

    • 你的意思是value而不是arg
    • @alani。我的意思是args[0]。现已修复
    • e.value 更好(args[0] 可以提高 IndexError 以获得裸回报)。另外值得一提的是:你只有一次机会得到这个,以后StopIteration就没有了。因此,如果生成器与 for 循环一起使用,您将不得不链接另一个生成器或使用装饰器或其他东西。我不是反对者。
    • @wim。很好看。将立即更新。
    • 我认为这是一个完整而准确的答案,而且看起来很尴尬,因为 Python 实现本身就很尴尬。
    【解决方案5】:

    yield 在你的函数中的存在使它变成了不同于常规函数的东西——python 称之为generator。您不能调用生成器并期望它像函数一样执行。除非在退化或简单化的情况下。 要在生成器生命周期结束时检索问题中的状态信息(如整个列表 l),您需要将其存储在变量中或每次在 yield 语句中返回。

    要在每次调用生成器时返回它:

    def gen_test():
        l = []
        for i in range(6):
            l.append(i)
            yield i, l
        
    for y,l in gen_test():
            print (y,l)
        
    

    否则,您可以将生成器声明为类的一部分并将中间结果存储在类变量中。然后稍后使用不同的调用检索该变量值。

    【讨论】:

    • 生成器的返回值是有意义的,虽然很少使用。它被分配给最后引发的StopIteration 的值。
    猜你喜欢
    • 2011-05-27
    • 2011-11-06
    • 1970-01-01
    • 2011-01-22
    • 2012-01-14
    • 1970-01-01
    • 2016-03-20
    • 1970-01-01
    • 2012-08-02
    相关资源
    最近更新 更多