【问题标题】:Why yield returns an iterator?为什么 yield 返回一个迭代器?
【发布时间】:2011-07-23 23:13:55
【问题描述】:

我正在尝试了解 yield 的工作原理,在阅读了 text 之后,我相信它是可以理解的。

但是我仍然不明白 yield 和 __iter__ 之间有什么联系,因为我刚刚发现这段代码有效:

class Vect():
    def __init__(self, *args):
        self.__a = list(args)
    def print_(self):
        print self.__a
    def __iter__(self):
        yield self.__a

asd = Vect(1,2,3,4,5)
for foo in asd:
    print foo

我认为,当我有一个生成器(一个在当时返回单个参数但返回尽可能多的参数直到它到达末尾的函数)时,yield 的工作方式类似于:“好的,让我们返回这个参数,但也许我们仍然可以返回另一个”。但是在我的示例中,我没有任何生成器,yield “返回”一个列表,并以某种方式访问​​列表的迭代器。我完全不知道发生了什么。

【问题讨论】:

  • 带有 yield 语句的方法是一个迭代器。产生的值是迭代器访问的值。
  • 顺便问一下,你们中的任何人都知道 Python 是如何记住迭代器产生的最后一个元素的吗?
  • “但是在我的示例中,我没有任何生成器” - 包含 yield 语句的函数是生成器。你期望需要什么?生成器表达式?那只是一种生成器。

标签: python yield


【解决方案1】:

yield 返回传递给它的任何对象,即使该对象是序列或生成器或其他迭代器。因此:

>>> def g():
...     yield [1,2,3]
...     yield 1
...     yield 2
...     yield 3
... 
>>> gen = g()
>>> gen.next()
[1, 2, 3]
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
3
>>> gen.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

__iter__ 在需要对对象内容的迭代器时在对象上调用(例如当它是for x in obj 构造的一部分时)。您可以使用yield 创建生成器(因为生成器是迭代器),但在本示例中您不需要这样做。以下内容也将起作用:

def __iter__(self):
   return iter(self.__a)

如果你想使用yield,并且你想让Vect对象的迭代器在向量的内容中移动,你必须分别产生每个值:

def __iter__(self):
    for i in self.__a:
        yield i

yield 表示 __iter__ 将返回一个生成器,并且在生成器对象上调用 next() 将在它上次停止的点恢复函数,因为它会遍历 __a。

=======

针对有关 Python 如何跟踪生成器执行位置的附加问题,我相信它使用了生成器的 gi_frame 属性的 f_lasti(==“最后一条指令”)(生成器,与普通函数不同,随身携带一个执行框架)。这里有一些工具可以显示值如何变化:

>>> import dis
>>> def g():
...     yield 1
...     for i in range(10):
...             yield i*2
... 
>>> gen = g() 
>>> dis.dis(gen.gi_code)
  2           0 LOAD_CONST               1 (1)
              3 YIELD_VALUE         
              4 POP_TOP             

  3           5 SETUP_LOOP              29 (to 37)
              8 LOAD_GLOBAL              0 (range)
              11 LOAD_CONST               2 (10)
              14 CALL_FUNCTION            1
              17 GET_ITER            
         >>   18 FOR_ITER                15 (to 36)
              21 STORE_FAST               0 (i)

   4          24 LOAD_FAST                0 (i)
              27 LOAD_CONST               3 (2)
              30 BINARY_MULTIPLY     
              31 YIELD_VALUE         
              32 POP_TOP             
              33 JUMP_ABSOLUTE           18
         >>   36 POP_BLOCK           
         >>   37 LOAD_CONST               0 (None)
              40 RETURN_VALUE        
>>> gen.gi_frame.f_lasti ## -1 because we haven't started yet
-1 
>>> gen.next()  
1
>>> gen.gi_frame.f_lasti
3
>>> gen.gi_frame.f_locals
{}
>>> gen.next() 
0
>>> gen.gi_frame.f_lasti , gen.gi_frame.f_locals
(31, {'i': 0})
>>> gen.next()
2
>>> gen.gi_frame.f_lasti , gen.gi_frame.f_locals
(31, {'i': 1})
>>> 

请注意 f_lasti 值如何对应于反汇编代码中最后一次 yield 所在的编号行:它在重新进入生成器时从该点重新启动。

【讨论】:

  • 感谢您的帖子,我能够完全理解 yield 的工作原理。谢谢。
【解决方案2】:

如果你检查你的打印,你会看到 iter 正在返回整个列表,当你打印 foo 时它只打印一次

看看这个:

for index, foo in enumerate(asd):
    print index, foo

【讨论】:

  • 是的,在显示器这边太晚了,我没有太注意,当 Python 没有给出错误时,我认为它是这样工作的。
【解决方案3】:

yield 完全按照您的理解工作 - 它返回一个对象,在您的情况下是一个列表。

也许这会让它更清楚 - 将您的代码更改为:

i = 1
for foo in asd:
    print i
    i += 1
    print foo

如您所见,只有一次迭代 - 迭代器包含一个元素 - 列表 [1,2,3,4,5]。

【讨论】:

    猜你喜欢
    • 2021-07-11
    • 1970-01-01
    • 2016-10-02
    • 2017-03-19
    • 2016-09-23
    • 1970-01-01
    • 1970-01-01
    • 2014-05-25
    • 2010-09-23
    相关资源
    最近更新 更多