以不同的方式实现`__iter__()`和`__next__()`

HER*_*ERO 7 python oop iterator

我正在读一本关于 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
Run Code Online (Sandbox Code Playgroud)

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

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

这是否意味着您可以将__iter__()和放入__next__()两个不同的对象中?可以对属于不同类的对象执行此操作吗?只能对属于不同类的对象执行此操作吗?这可能是实现迭代器协议的有点奇怪的方式。但我只是想看看如何实现,前提是它实际上可以这样实现。

Sha*_*ger 18

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

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

  1. 实现__iter__return self,仅__next__在同一个类上实现。您已经编写了一个迭代
  2. 实现__iter__返回一些遵循 #1 规则的其他对象(一种便宜的方法是将其编写为生成器函数,这样您就不必手动实现其他类)。不执行__next__. 您编写了一个不是迭代器的迭代

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

在情况 #2 中,根据定义,另一个对象将属于另一个类(因为您要么具有幂等性__iter__并实现__next__,要么只有__iter__, without __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__
Run Code Online (Sandbox Code Playgroud)

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

 for x in iterable:
     for y in iterable:  # Operating over product of all elements in iterable
Run Code Online (Sandbox Code Playgroud)

如果__iter__将自身重置为开始并且只有一个状态,则将:

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

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


直接回答您的问题:

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

对于迭代,你不能(它必须有两种方法,尽管__iter__很简单)。对于非迭代iterable s,您必须(它必须具有__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
Run Code Online (Sandbox Code Playgroud)

迭代器的示例:

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
Run Code Online (Sandbox Code Playgroud)

“简单迭代”的示例,其中您不通过创建__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
Run Code Online (Sandbox Code Playgroud)