【问题标题】:Python: yield and yield assignmentPython:产量和产量分配
【发布时间】:2015-11-14 16:41:49
【问题描述】:

这段涉及赋值和yield 运算符的代码是如何工作的?结果相当令人困惑。

def test1(x): 
    for i in x:
        _ = yield i 
        yield _
def test2(x): 
    for i in x:
        _ = yield i 

r1 = test1([1,2,3])
r2 = test2([1,2,3])
print list(r1)
print list(r2)

输出:

[1, None, 2, None, 3, None] 
[1, 2, 3]

【问题讨论】:

  • 我不太确定为什么,但最有可能出现在任何 Python 问题的“相关”列表中的一个问题是 the yield question,即使该问题与yield。不过这一次,确实是相关的。
  • @TigerhawkT3:相关问题是由于有人从一个问题链接到另一个问题。所以想必有很多cmet都指向这个问题。

标签: python yield assignment-operator


【解决方案1】:

赋值语法(“yield 表达式”)允许您将生成器视为基本协程。

首先在PEP 342 中提出并记录在此:https://docs.python.org/2/reference/expressions.html#yield-expressions

与生成器一起工作的客户端代码可以使用其send() 方法将数据传回生成器。该数据可通过赋值语法访问。

send() 也会迭代 - 所以它实际上包含一个 next() 调用。

使用您的示例,这就是使用 couroutine 功能的样子:

>>> def test1(x):
...     for i in x:
...         _ = yield i
...         yield _
...
>>> l = [1,2,3]
>>> gen_instance = test1(l)

>>> #First send has to be a None
>>> print gen_instance.send(None)
1
>>> print gen_instance.send("A")
A
>>> print gen_instance.send("B")
2
>>> print gen_instance.send("C")
C
>>> print gen_instance.send("D")
3
>>> print gen_instance.send("E")
E
>>> print gen_instance.send("F")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

请注意,由于每个循环迭代中的第二个 yield 未捕获发送的数据,因此某些发送会丢失。

编辑: 忘了解释你的例子中产生的Nones。

来自https://docs.python.org/2/reference/expressions.html#generator.next

当使用 next() 方法恢复生成器函数时,当前 yield 表达式的计算结果始终为 None。

next() 在使用迭代语法时使用。

【讨论】:

    【解决方案2】:

    为了扩展 TigerhawkT3 的答案,yield 操作在您的代码中返回 None 的原因是因为 list(r1) 没有向生成器发送任何内容。试试这个:

    def test1(x):
        for i in x:
            _ = yield i
            yield _
    
    
    r1 = test1([1, 2, 3])
    
    for x in r1:
        print('   x', x)
        print('send', r1.send('hello!'))
    

    输出:

       x 1
    send hello!
       x 2
    send hello!
       x 3
    send hello!
    

    这里有一个稍微虚构的例子,将值发送到生成器可能很有用:

    def changeable_count(start=0):
        current = start
        while True:
            changed_current = yield current
            if changed_current:
                current = changed_current
            else:
                current += 1
    
    counter = changeable_count(10)
    
    for x in range(20):
        print(next(counter), end=' ')
    
    print()
    print()
    
    print('Sending 51, printing return value:', counter.send(51))
    print()
    
    for x in range(20):
        print(next(counter), end=' ')
    
    print()
    print()
    
    print('Sending 42, NOT printing return value')
    print()
    
    counter.send(42)
    
    for x in range(20):
        print(next(counter), end=' ')
    
    print()
    

    输出:

    10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 
    
    Sending 51, printing return value: 51
    
    52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 
    
    Sending 42, NOT printing return value
    
    43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
    

    【讨论】:

      【解决方案3】:
      _ = yield i 
      yield _
      

      首先是yields i 引用的值,例如1。然后它产生yield 操作返回的值,即None。它在循环的每次迭代中执行此操作。

      for i in x:
          _ = yield i
      

      这只是yields i 引用的值,例如1,然后继续循环的下一次迭代,产生2,然后是3

      return 不同,yield 关键字可用于表达式:

      x = return 0 # SyntaxError
      x = yield 0 # perfectly fine
      

      现在,当解释器看到yield 时,它将生成指示的值。但是,当它这样做时,该操作返回值None,就像mylist.append(0)print('hello')returnNone。当您将该结果分配给 _ 之类的引用时,您将保存该 None

      因此,在第一个 sn-p 中,您将生成一个对象,然后保存 yield 操作的“结果”,即 None,然后您将 yield 保存为 None。在第二个 sn-p 中,您生成一个对象,然后保存该 yield 操作的“结果”,但您从未yield 该结果,因此None 不会出现在输出。

      请注意,yield 不会总是返回 None - 这正是您使用 send() 发送到生成器的内容。因为在这种情况下这没什么,所以你得到None。有关send() 的更多信息,请参阅this answer

      【讨论】:

      • @JohnKugelman - 当然,我会添加一些更详细的解释。
      • @JohnKugelman - 已添加信息。如果任何措辞不清楚或具有误导性,请告诉我。
      • 给我最清楚的解释,非常感谢。
      猜你喜欢
      • 2016-06-08
      • 2018-02-04
      • 2021-02-23
      • 2011-09-29
      • 1970-01-01
      • 2016-08-25
      • 1970-01-01
      • 2017-04-17
      • 1970-01-01
      相关资源
      最近更新 更多