【问题标题】:__next__ in generators and iterators and what is a method-wrapper?__next__ 在生成器和迭代器中,什么是方法包装器?
【发布时间】:2017-03-08 09:29:57
【问题描述】:

我正在阅读有关生成器和迭代器以及 __next__() 的角色的信息。

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

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

当我深入研究它时,

'__next__' in dir (mylist.__iter__()) 为真

  1. 为什么__next__ 仅适用于列表,但仅适用于 __iter__()mygen 而不是 mylist。当我们使用 list-comprehension 遍历列表时,__iter__() 如何调用 __next__

    尝试手动提升 (+1) 生成器,我打电话给 mygen.__next__()。它不存在。它只作为mygen.__next__存在,称为method-wrapper。

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

  3. 如果__next__ 是生成器和迭代器都提供的(以及它们唯一的属性),那么生成器和迭代器有什么区别?*

    答案 3:已解决,如 mod/editor 所述:

    Difference between Python's Generators and Iterators

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

感谢大家的回答!

【问题讨论】:

  • "尝试手动升级 (+1) 生成器,我打电话给 mygen.__next__()。它不存在。" - 是的。如果看起来没有,那你就搞砸了。
  • 你是对的。 a=[1,2,3,4].__iter__() a.__next__() 产生了 Out[1] 1 显然正在加紧。但是另一个mygen.__next__() 失败了:StopIteration <snip> ---> 14 mygen.__next__() StopIteration: 我也忽略了这个错误,并认为它丢失了。但事实并非如此。这只是 stopIteration 错误。我仍然不明白为什么当__next__() 还没有被调用过时它会抛出停止迭代异常。
  • 好吧,mygen.__next__() 现在可以工作了。它早些时候给了我stopiteration exception。我无法复制它。感谢您的提示。
  • @theMobDog __next__ 将在迭代器结束时抛出 StopIteration 异常。如果要再次迭代对象,则需要创建一个新的迭代器。

标签: python python-3.x iterator generator wrapper


【解决方案1】:

特殊方法__iter____next__ 是创建iterator types 的迭代器协议的一部分。为此,您必须区分两个不同的事物: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__ 的原因:否则您永远无法循环遍历迭代器。


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


一个示例生成器函数如下:

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

包含yield 语句的函数体将其转换为生成器函数。这意味着当你调用exampleGenerator() 时,你会得到一个generator 对象。生成器对象实现了迭代器协议,所以我们可以在它上面调用__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() 调用尚未打印任何内容。这是生成器的特殊之处:它们是惰性的,只评估从可迭代对象中获取下一项所需的值。只有通过第二次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

【讨论】:

  • 哇!太感谢了。那是相当广泛的!我现在正在阅读。
【解决方案2】:

为什么__next__ 仅可用于列出但仅可用于__iter__()mygen 而不是mylist。当我们使用 list-comprehension 遍历列表时,__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 中实现的方法。这就是iter(list).__iter__list 是在C 中实现的对象)和gen.__iter__(这里不确定,但可能也是生成器)是什么。

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

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

【讨论】:

  • 感谢iter 致电next 的详细步骤。澄清一下,在列表理解 x for x in mylist 的某个地方,iter 对象被返回并开始调用 next?
【解决方案3】:

__next____iter__ 是您执行 next(some_gen)iter(some_sequence) 时的方法包装器。 next(some_gen)some_gen.__next__() 相同

所以如果我做mygen = iter(mylist) 那么mygenmylist 实现为生成器对象并具有__next__ 方法描述符。列表本身没有此方法,因为它们不是生成器。

生成器是迭代器。查看difference between generators and iterators

【讨论】:

  • 很好地解释了 mygen 和 mylist 之间的关系。和mygen=iter(mylist)。谢谢。
猜你喜欢
  • 2020-06-29
  • 2017-09-29
  • 2010-11-04
  • 1970-01-01
  • 2019-10-25
  • 1970-01-01
  • 2018-08-16
  • 2011-11-05
  • 2015-01-27
相关资源
最近更新 更多