首页 > 解决方案 > 以不同的方式实现 `__iter__()` 和 `__next__()`

问题描述

我正在阅读一本关于 Python 的书,它说明了如何实现迭代器协议。

class Fibbs:
    def __init__(self):
        self.a = 0
        self.b = 1
    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        return self.a
    def __iter__(self):
        return self

在这里,self我相信它本身就是可迭代和迭代器?但是,下面的段落说:

请注意,迭代器实现了该__iter__方法,实际上,该方法将返回迭代器本身。在许多情况下,您会将__iter__方法放在另一个对象中,您将在 for 循环中使用该对象。然后将返回您的迭代器。建议迭代器__iter__另外实现自己的方法(返回self,就像我这里做的那样),这样就可以直接在for循环中使用它们自己。

这是否意味着您可以放入两个不同__iter__()__next__()对象?可以为属于不同类的对象完成吗?只能对属于不同类的对象进行吗实现迭代器协议的方式可能有点奇怪。但我只是想看看如何,只要它实际上可以这样实现。

标签: pythonoopiterator

解决方案


如何制作迭代器和可迭代对象

有两种方法可以做到这一点:

  1. 实现__iter__,仅此return self而已,__next__在同一个类上实现。你写了一个迭代
  2. 实现__iter__以返回遵循 #1 规则的其他对象(一种廉价的方法是将其编写为生成器函数,这样您就不必手动实现其他类)。不要实施__next__. 你写了一个不是迭代器的迭代

对于每个协议的正确实现版本,区分它们的方式就是__iter__方法。如果主体只是return self可能带有日志语句或其他东西,但没有其他副作用),那么它要么是迭代器,要么写得不正确。如果主体是其他任何东西,那么它要么是非迭代器可迭代的,要么写得不正确。其他任何事情都违反了协议的要求。

在 #2 的情况下,根据定义,另一个对象将属于另一个类(因为您要么具有幂等性__iter__并实现__next__,要么只有__iter__,没有__next__,这会产生一个新的迭代器)。


为什么协议是这样设计的

__iter__甚至需要迭代器的原因是支持以下模式:

 iterable = MyIterable(...)
 iterator = iter(iterable)  # Invokes MyIterable.__iter__
 next(iterator, None)  # Throw away first item
 for x in iterator:    # for implicitly calls iterator's __iter__; dies if you don't provide __iter__

您总是为可迭代对象返回一个新的迭代器,而不是仅仅使它们成为迭代器并在__iter__调用时重置状态的原因是为了处理上述情况(如果MyIterable只是返回自身并重置迭代,for循环的隐式调用__iter__将再次重置它并撤消第一个元素的预期跳过)并支持这样的模式:

 for x in iterable:
     for y in iterable:  # Operating over product of all elements in iterable

如果__iter__将自身重置到开头并且只有一个状态,这将:

  1. 获取第一个项目并将其放入x
  2. 重置,然后遍历iterable将每个值放入的整个过程y
  3. 尝试继续外循环,发现它已经用尽了,永远不要给任何其他价值x

它也是必需的,因为 Python 假定这iter(x) is x是一种安全、无副作用的方法来测试可迭代对象是否是迭代器。如果你__iter__修改了自己的状态,它不是没有副作用的。在最坏的情况下,对于可迭代对象,它应该浪费一点时间来创建一个立即被丢弃的迭代器。对于迭代器,它实际上应该是免费的(因为它只是返回自己)。


直接回答您的问题:

这是否意味着您可以放入两个不同__iter__()__next__()对象?

对于 itera s,你不能(它必须有两种方法,虽然__iter__很简单)。对于非迭代的迭代,您必须(它必须只有__iter__并返回一些其他迭代对象)。没有“可以”。

可以为属于不同类的对象完成吗?

是的。

只能对属于不同类的对象进行吗?

是的。


例子

可迭代的示例:

class MyRange:
    def __init__(self, start, stop):
         self.start = start
         self.stop = stop

    def __iter__(self):
         return MyRangeIterator(self)  # Returns new iterator, as this is a non-iterator iterable

    # Likely to have other methods (because iterables are often collections of
    # some sort and support many other behaviors)
    # Does *not* have __next__, as this is not an iterator

迭代器示例:

class MyRangeIterator:  # Class is often non-public and or defined inside the iterable as
                        # nested class; it exists solely to store state for iterator
    def __init__(self, rangeobj):  # Constructed from iterable; could pass raw values if you preferred
        self.current = rangeobj.start
        self.stop = rangeobj.stop
    def __iter__(self):
        return self             # Returns self, because this is an iterator
    def __next__(self):         # Has __next__ because this is an iterator
        retval = self.current   # Must cache current because we need to modify it before we return
        if retval >= self.stop:
            raise StopIteration # Indicates iterator exhausted
        self.current += 1       # Ensure state updated for next call
        return retval           # Return cached value

    # Unlikely to have other methods; iterators are generally iterated and that's it

通过创建生成器函数,您不实现自己的迭代器类的“简单可迭代”示例__iter__

class MyEasyRange:
    def __init__(self, start, stop): ... # Same as for MyRange

    def __iter__(self):  # Generator function is simpler (and faster)
                         # than writing your own iterator class
         current = self.start  # Can't mutate attributes, because multiple iterators might rely on this one iterable
         while current < self.stop:
             yield current     # Produces value and freezes generator until iteration resumes
             current += 1
         # reaching the end of the function acts as implicit StopIteration for a generator

推荐阅读