生成器和迭代器中的 __next__ 以及什么是方法包装器?

新手上路,请多包涵

我正在阅读生成器和迭代器以及 __next__() 的作用。

'__next__' in dir(mygen) 。是真的

'__next__' in dir(mylist) ,是假的

当我深入研究它时,

'__next__' in dir (mylist.__iter__()) 是真的

  1. why is __next__ only available to list but only to __iter__() and mygen but not mylist .当我们使用列表理解遍历列表时, __iter__() 如何调用 __next__

尝试手动步进 (+1) 生成器,我调用了 mygen.__next__() 。它不存在。它仅作为 mygen.__next__ 存在,称为方法包装器。

  1. 什么是方法包装器,它有什么作用?它在这里是如何应用的:在 mygen() and __iter__() ?

  2. 如果 __next__ 是生成器和迭代器提供的(及其唯一属性)那么生成器和迭代器之间有什么区别?*

对 3 的回答:已解决,如 mod/editor 所述:

Python 的生成器和迭代器之间的区别

更新:生成器和迭代器都有 __next__() 。我的错。查看日志,不知何故 mygen.__next__() 测试给我停止迭代异常错误。但我无法再次复制该错误。

谢谢大家的回答!

原文由 theMobDog 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 666
2 个回答

特殊方法 __iter____next__ 是创建 迭代器类型 的迭代器协议的一部分。为此,您必须区分两个不同的事物: Iterablesiterators

Iterables 是可以迭代的东西,通常,这些是某种包含项目的容器元素。常见的例子是列表、元组或字典。

为了迭代可迭代对象,您可以使用 _迭代器_。迭代器是帮助您遍历容器的对象。例如,在迭代列表时,迭代器本质上会跟踪您当前所在的索引。

要获得迭代器,需要在可迭代对象上调用 __iter__ 方法。这就像一个工厂方法,为这个特定的可迭代对象返回一个新的迭代器。定义了 __iter__ 方法的类型,将其转换为可迭代对象。

迭代器通常需要一个方法 __next__ ,它返回迭代的 下一个 项目。此外,为了使协议更易于使用,每个迭代器也应该是可迭代的,在 __iter__ 方法中返回自身。

作为一个简单的例子,这将是一个可能的列表迭代器实现:

 class ListIterator:
    def __init__ (self, lst):
        self.lst = lst
        self.idx = 0

    def __iter__ (self):
        return self

    def __next__ (self):
        try:
            item = self.lst[self.idx]
        except IndexError:
            raise StopIteration()
        self.idx += 1
        return item

然后,列表实现可以简单地从 __iter__ 方法返回 ListIterator(self) 。当然,列表的实际实现是用 C 语言完成的,所以这看起来有点不同。但思路是一样的。

迭代器在 Python 的各个地方都以不可见的方式使用。例如 for 循环:

 for item in lst:
    print(item)

这与以下内容有点相同:

 lst_iterator = iter(lst) # this just calls `lst.__iter__()`
while True:
    try:
        item = next(lst_iterator) # lst_iterator.__next__()
    except StopIteration:
        break
    else:
        print(item)

因此 for 循环从可迭代对象请求一个迭代器,然后调用 __next__ 直到它命中 StopIteration 异常。这在表面下发生也是您希望迭代器也实现 __iter__ 的原因:否则您永远无法循环遍历迭代器。


至于生成器,人们通常所说的其实是一个生成器 _函数_,即一些函数定义有 yield 语句。一旦你调用那个生成器函数,你就会得到一个 _生成器_。生成器本质上只是一个迭代器,尽管是一个奇特的迭代器(因为它不仅仅是在容器中移动)。作为迭代器,它有一个 __next__ 方法来“生成”下一个元素,还有一个 __iter__ 方法返回它自己。


生成器函数示例如下:

 def exampleGenerator():
    yield 1
    print('After 1')
    yield 2
    print('After 2')

包含 yield 语句的函数体将其转换为生成器函数。这意味着当您调用 exampleGenerator() 时,您会得到一个 生成器 对象。生成器对象实现了迭代器协议,因此我们可以在其上调用 __next__ (或使用上面的 next() 函数):

 >>> x = exampleGenerator()
>>> next(x)
1
>>> next(x)
After 1
2
>>> next(x)
After 2
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    next(x)
StopIteration

请注意,第一个 next() 调用尚未打印任何内容。这是生成器的特殊之处:它们是惰性的,只计算必要的次数以从 iterable 中获取下一个项目。只有在第二次调用 next() 时,我们才能从函数体中获得第一行打印。我们需要另一个 next() 调用来耗尽可迭代对象(因为没有产生另一个值)。

但除了这种懒惰之外,生成器就像可迭代对象一样。你甚至在最后得到一个 StopIteration 异常,它允许生成器(和生成器函数)用作 for 循环源和任何可以使用“正常”迭代的地方。

生成器及其惰性的最大好处是能够 按需 生成内容。一个很好的类比是在网站上无休止地滚动:你可以一个接一个地向下滚动项目(在生成器上调用 next() ),并且每隔一段时间,网站将不得不查询后端以检索更多供您滚动浏览的项目。理想情况下,这种情况会在您不注意的情况下发生。而这正是生成器所做的。它甚至允许这样的事情:

 def counter():
    x = 0
    while True:
        x += 1
        yield x

非懒惰,这是不可能计算的,因为这是一个无限循环。但是懒惰地,作为一个生成器,可以一个接一个地消费这个迭代的项目。我最初不想让您将此生成器实现为完全自定义的迭代器类型,但在这种情况下,这实际上并不太难,所以它是这样的:

 class CounterGenerator:
    def __init__ (self):
        self.x = 0

    def __iter__ (self):
        return self

    def __next__ (self):
        self.x += 1
        return self.x

原文由 poke 发布,翻译遵循 CC BY-SA 3.0 许可协议

为什么 mylist mygen __next__ 仅适用于列表,但仅适用于 __iter__() 当我们使用列表理解遍历列表时, __iter__() 如何调用 __next__

因为列表有一个从 iter 返回的单独对象来处理迭代,这个对象 __iter__ 被连续调用。

所以,对于列表:

 iter(l) is l # False, returns <list-iterator object at..>

而对于生成器:

 iter(g) is g # True, its the same object

在循环构造中, iter 首先将在要循环的目标对象上被调用。 iter 调用 __iter__ 并期望返回一个迭代器;它的 __next__ 被调用直到没有更多元素可用。

什么是方法包装器,它有什么作用?它在这里是如何应用的:在 mygen()__iter__() 中?

如果我没记错的话,方法包装器是在 C 中实现的方法。 Which is what both these iter(list).__iter__ ( list is an object implemented in C ) and gen.__iter__ (not sure here but generators are probably too ) 是。

如果 __next__ 是生成器和迭代器都提供的(及其唯一属性),那么生成器和迭代器之间有什么区别?

生成器是迭代器,正如 iter(l) 提供的迭代器一样。它是一个迭代器,因为它提供了一个 __next__ 方法(通常,当在 for 循环中使用时,它能够提供值直到耗尽)。

原文由 Dimitris Fasarakis Hilliard 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题