【问题标题】:Behaviour of Python's "yield"Python 的“收益”行为
【发布时间】:2011-11-13 20:13:18
【问题描述】:

我正在阅读 python 中的 yield 关键字,并试图理解运行此示例:

def countfrom(n):
    while True:
        print "before yield"
        yield n
        n += 1
        print "after yield"

for i in countfrom(10):
    print "enter for loop"
    if i <= 20:
        print i
    else:
        break

输出是:

before yield
enter for loop
10
after yield
before yield
enter for loop
11
after yield
before yield
enter for loop
12
after yield
before yield
enter for loop
13
after yield
before yield
enter for loop
14
after yield
before yield
enter for loop
15
after yield
before yield
enter for loop
16
after yield
before yield
enter for loop
17
after yield
before yield
enter for loop
18
after yield
before yield
enter for loop
19
after yield
before yield
enter for loop
20
after yield
before yield
enter for loop

看起来 yield 将返回指定的值,并将继续运行函数直到结束(可能在并行线程中)。我的理解正确吗?

如果你能在不提及“生成器”的情况下回答这个问题,我将不胜感激,因为我试图一次理解一个。

【问题讨论】:

  • stackoverflow.com/questions/231767/…。在没有生成器的情况下解释 yield 是没有意义的,因为 yield 的主要(并且在大多数情况下是唯一的)目的是创建生成器。
  • -1:“如果你能在不提及“生成器”的情况下回答这个问题。”做不到。

标签: python yield


【解决方案1】:

有和没有 for 循环的产量:

def f1():
    print('f1')
    yield 10
    print(f'f1 done')


def generator_with_for_loop():
    print(f'generator_with_for_loop')
    for f1_gen in f1():
        print(f'f1_gen={f1_gen}')


def generator_without_for_loop():
    print(f'\ngenerator_without_for_loop')
    gen = f1()
    print(f'f1_gen={gen}')
    print(gen.__next__())
    try:
        print(gen.__next__())
    except StopIteration:
        print('done')


if __name__ == '__main__':
    generator_with_for_loop()
    generator_without_for_loop()

"""
generator_with_for_loop
f1
f1_gen=10
f1 done

generator_without_for_loop
f1_gen=<generator object f1 at 0x7fd7201e54a0>
f1
10
f1 done
done
"""

【讨论】:

    【解决方案2】:

    Python 一直运行直到遇到yield,然后停止并冻结执行。它不会继续运行。在下一次调用countfrom时,它会点击“之后”

    说起来很容易,不用提到生成器,但事实是,yield 和生成器是密不可分的。要真正理解它,您必须将它们视为同一主题。

    通过以更手动的方式使用示例中的生成器,很容易向自己证明我(和其他人)所说的是真实的。

    yields 而不是returning 的函数真正返回了一个生成器。然后,您可以通过调用 next 来使用该生成器。你很困惑,因为你的循环在后台为你处理了所有这些。

    这是内部打开的:

    def countfrom(n):
        while n <= 12:
            print "before yield, n = ", n
            yield n
            n += 1
            print "after yield, n = ", n
    
    
    your_generator = countfrom(10)
    next(your_generator)
    print "see the after yield hasn't shown up yet, it's stopped at the first yield"
    next(your_generator)
    print "now it woke back up and printed the after... and continued through the loop until it got to back to yield"
    next(your_generator)
    print "rinse and repeate"
    

    【讨论】:

      【解决方案3】:

      ..如果不提及生成器,您将无法解释 yield 语句的含义;这就像试图解释石头是什么而不提到石头一样。即:yield 语句负责将普通函数转换为生成器。

      虽然您在此处找到了详细记录:http://docs.python.org/reference/simple_stmts.html#the-yield-statement

      ..它的简要说明是:

      • 当调用使用 yield 语句的函数时,它返回一个“生成器迭代器”,具有 .next() 方法(可迭代对象的标准)
      • 每次调用生成器的.next() 方法时(例如,通过使用for 循环迭代对象),都会调用该函数,直到遇到第一个yield。然后暂停函数执行,并传递一个值作为.next() 方法的返回值。
      • 下次调用.next() 时,会继续执行函数,直到下一次yield 等,直到函数返回某些内容。

      这样做的一些好处是:

      • 更少的内存使用,因为内存只分配给当前产生的值,而不是整个返回值列表(因为它会返回一个值列表)
      • “实时”结果返回,因为它们可以被传递给调用者而无需等待生成结束(我用它来从正在运行的进程返回输出)

      【讨论】:

      • 尽管我打算使用火花塞和发动机,但你在“用另一个解释”的比喻上打败了我。这篇较早的文章也很好地概述了产量和生成器:ibm.com/developerworks/library/l-pycon/index.html
      • 是的,但是柴油/蒸汽/喷气/..发动机没有火花塞;虽然产量和生成器是相互依赖的 :) 顺便说一句,我喜欢 IBM developerworks 文章 :)
      【解决方案4】:

      函数countfrom 未在并行线程中运行。这里发生的情况是,每当for-construct 请求下一个值时,函数都会执行,直到遇到yield 语句。当需要之后的下一个值时,函数会从中断处继续执行。

      虽然您要求不要提及“生成器”,但它们与 yield 的联系如此密切,因此单独讨论它们并没有任何意义。您的 countfrom 函数实际上返回的是“生成器对象”。它在调用后立即返回此对象,因此函数体根本不会执行,直到某些东西(例如 for-loop)使用其方法 .next() 从生成器请求值。

      【讨论】:

        【解决方案5】:

        不,只有一个线程。

        for 循环的每次迭代都会运行您的 countFrom 函数,直到它产生某些结果或返回。在 yield 之后,for 循环的主体再次运行,然后,当新的迭代开始时,countFrom 函数会从它停止的地方重新开始并再次运行,直到它产生(或返回)。

        您的示例的这个修改版本将有助于更清楚地执行路径。

        def countfrom(n):
            while n <= 12:
                print "before yield, n = ", n
                yield n
                n += 1
                print "after yield, n = ", n
        
        for i in countfrom(10):
            print "enter for loop, i = ", i
            print i
            print "end of for loop iteration, i = ", i
        

        输出

        before yield, n =  10
        enter for loop, i =  10
        10
        end of for loop iteration, i =  10
        after yield, n =  11
        before yield, n =  11
        enter for loop, i =  11
        11
        end of for loop iteration, i =  11
        after yield, n =  12
        before yield, n =  12
        enter for loop, i =  12
        12
        end of for loop iteration, i =  12
        after yield, n =  13
        

        【讨论】:

        • 很好的解释。因此函数在 yield 语句处立即暂停,其余部分将在下一次迭代中进行。
        【解决方案6】:

        你可以把它想象成yields 在遇到yield 时简单地“暂停”的函数。下次您调用它时,它将在yield 之后恢复保持它离开时的状态

        【讨论】:

        • 所以产量就像return 一样工作,它会从该行继续,在下次调用时保持该值?
        • 不,你不要再次调用该函数才能恢复,你必须在返回的生成器对象上调用next()方法..
        • @redShadow 但在我的示例中没有调用next(),那么它是如何恢复的?
        • @Tom Brito,next()for 循环在后台执行。也就是说,“可迭代”对象只是一个带有next() 方法的对象,它应该为它包含的每个项目返回一个值,并在到达最后一个项目时引发异常
        • @Tom:解释next 需要使用你不想听到的词,所以我写了“next call”。对不起,如果这比帮助更令人困惑......
        【解决方案7】:

        yield 语句存储您产生的值,直到再次调用该函数。 因此,如果您调用该函数(使用迭代器),它将再次运行该函数并为您提供值。 关键是它知道上次停止的地方

        【讨论】:

        • 哦,我想我明白了,yield 的工作方式类似于 return,并且它会在下次调用时从 yield 行继续?
        • 否,但它会知道上次“n”的值是多少。在这个例子中它似乎无关紧要,因为你可以将 n 作为参数传递,但它稍后会变得非常有用
        • 对不起,我想我误解了你的评论。你的想法是正确的(对不起)
        • 看起来这里的生成器有点混乱(我也花了一些时间才完全理解它们):状态不是在函数的多次调用之间保持,而是在返回的生成器对象的 .next() 方法。当然,这是通过迭代其返回值来完成的..
        猜你喜欢
        • 2013-12-11
        • 2021-08-23
        • 2020-07-22
        • 2010-12-21
        • 1970-01-01
        • 2014-10-20
        • 2013-07-03
        • 1970-01-01
        • 2019-09-19
        相关资源
        最近更新 更多