为什么Python中有__iter__方法?

时间:2016-04-17 19:41:15

标签: python iterator

为什么要使用__iter__方法?如果一个对象是一个迭代器,那么拥有一个返回自身的方法是没有意义的。如果它不是一个迭代器而是一个可迭代的,即带有__iter____getitem__方法的东西,那么为什么人们想要定义返回迭代器但不是迭代器本身的东西呢?在Python中,何时想要定义一个本身不是迭代器的迭代?或者,什么是可迭代但不是迭代器的东西的例子?

3 个答案:

答案 0 :(得分:4)

尝试一次回答一个问题:

  

为什么要使用__iter__方法?如果一个对象是迭代器,那么有一个返回自身的方法是没有意义的。

这不是没有意义的。迭代器协议需要__iter____next__(或Python 2中的next)方法。我在其return self方法中看到的所有理智迭代器只有__iter__,但使用该方法仍然至关重要。没有它会导致各种奇怪,例如:

somelist = [1, 2, 3]
it = iter(somelist)

现在

iter(it)

for x in it: pass

会抛出TypeError并抱怨it不可迭代,因为当调用iter(x)时(当你使用for循环时会隐式发生),它会期望参数对象x能够生成迭代器(它只是尝试在该对象上调用__iter__)。具体示例(Python 3):

>>> class A:
...     def __iter__(self):
...         return B()
...
>>> class B:
...     def __next__(self):
...         pass
...
>>> iter(iter(A()))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'B' object is not iterable

考虑任何函数,特别是来自期望可迭代的itertools的函数,例如dropwhile。使用具有__iter__方法的任何对象调用它都没关系,无论它是不是迭代器的迭代器还是迭代器 - 因为调用{{1时可以得到相同的结果将该对象作为参数。在这里对两种迭代进行奇怪的区分将违背python强烈拥抱的duck typing原则。

这样的巧妙技巧
iter
如果你无法将迭代器传递给>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> list(zip(*[iter(a)]*3)) [(1, 2, 3), (4, 5, 6), (7, 8, 9)]

就会停止工作。

  

为什么人们想要定义返回迭代器但不是迭代器本身的东西

让我们考虑一下这个简单的列表迭代器:

zip

请记住,>>> class MyList(list): ... def __iter__(self): ... return MyListIterator(self) >>> >>> class MyListIterator: ... def __init__(self, lst): ... self._lst = lst ... self.index = 0 ... def __iter__(self): ... return self ... def __next__(self): ... try: ... n = self._lst[self.index] ... self.index += 1 ... return n ... except IndexError: ... raise StopIteration >>> >>> a = MyList([1,2,3]) >>> for x in a: ... for x in a: ... x ... 1 2 3 1 2 3 1 2 3 调用iter循环中的可迭代问题,每次从对象{{1}开始期待迭代器方法。

现在,每次使用for循环时都不会生成迭代器,当__iter__对象迭代到任意时,您将如何跟踪任何迭代的当前状态同时多少次?哦,没错,你不能。 :)

编辑:对Tadhg McDonald-Jensen评论的回复和奖励

可恢复的迭代器并不是不可想象的,但当然有点奇怪,因为它依赖于使用&#34;非消耗品&#34;可迭代的(即不是经典的迭代器):

for

答案 1 :(得分:1)

可迭代是可以迭代(循环)的东西,其中迭代器是消耗的东西。

  

什么是可迭代但不是迭代器的东西的例子?

简单,list。或者任何序列,因为您可以根据需要多次遍历列表而不会破坏列表:

>>> a = [1,2,3]
>>> for i in a:
    print(i,end=" ")

1 2 3 
>>> for i in a:
    print(i,end=" ")

1 2 3 

迭代器(如生成器)只能使用一次:

>>> b = (i for i in range(3))
>>> for i in b:
    print(i,end=" ")

0 1 2 
>>> for i in b:
    print(i,end=" ")


>>> #iterator has already been used up, nothing gets printed

对于像迭代器一样使用的列表,您需要使用self.pop(0)之类的东西来删除列表中的第一个元素进行迭代:

class IteratorList(list):
    def __iter__(self):
        return self #since the current mechanics require this
    def __next__(self):
        try:
            return self.pop(0)
        except IndexError: #we need to raise the expected kind of error
            raise StopIteration
    next = __next__ #for compatibility with python 2

a = IteratorList([1,2,3,4,5])

for i in a:
    print(i)
    if i==3:  # lets stop at three and
        break # see what the list is after

print(a)

给出了这个输出:

1
2
3
[4, 5]
你知道吗?这是迭代器的作用,一旦从__next__返回一个值,它就没有理由在迭代器或内存中挂起,所以它被删除了。这就是为什么我们需要__iter__来定义迭代器,让我们迭代序列而不会在过程中销毁它们。

在回复@timgeb's comment时,我想如果您将项目添加到IteratorList,然后再次对其进行迭代,那将是有意义的:

a = IteratorList([1,2,3,4,5])

for i in a:
    print(i)

a.extend([6,7,8,9])

for i in a:
    print(i)

但是所有迭代器只对消耗或永不结束才有意义。 (如itertools.repeat

答案 2 :(得分:0)

您的思维方向错误。迭代器必须实现__iter__的原因是,这样,容器迭代器都可以在for和{{1}中使用}。

in

这也是为什么您几乎需要在迭代器的几乎所有实现中返回> # list is a container > list = [1,2,3] > dir(list) [..., '__iter__', '__getitem__', ...] > # let's get its iterator > it = iter(list) > dir(it) [..., '__iter__', '__next__', ...] > # you can use the container directly: > for i in list: > print(i) 1 2 3 > # you can also use the iterator directly: > for i in it: > print(i) 1 2 3 > # the above will fail if it does not implement '__iter__' 的原因。它并不意味着任何时髦,只是语法上的一点简单。

参考:here