分离 __iter__ 和 __next__ 方法

Posted

技术标签:

【中文标题】分离 __iter__ 和 __next__ 方法【英文标题】:Separating the __iter__ and __next__ methods 【发布时间】:2019-02-02 23:39:12 【问题描述】:

在 Python 3 中,通过定义 __iter____next__ 方法使类同时成为可迭代和迭代器是标准过程。但是我有问题要解决这个问题。举个例子,它创建了一个只产生偶数的迭代器:

class EvenNumbers:
    
    def __init__(self, max_):
        self.max_ = max_

    def __iter__(self):
        self.n = 0
        return self

    def __next__(self):
        if self.n <= self.max_:  # edit: self.max --> self.max_
            result = 2 * self.n
            self.n += 1
            return result

        raise StopIteration

instance = EvenNumbers(4)

for entry in instance:
    print(entry)

据我所知(如果我错了,请纠正我),当我创建循环时,会通过调用 itr = iter(instance) 之类的东西来创建迭代器,它在内部调用 __iter__ 方法。这预计会返回一个迭代器对象(该实例是由于定义了__next__,因此我可以只返回self)。要从中获取元素,将调用 next(itr) 直到引发异常。

我现在的问题是:如果以及如何将__iter____next__ 分开,以便在其他地方定义后一个函数的内容?这什么时候有用?我知道我必须更改 __iter__ 以便它返回一个迭代器。

顺便说一句,这样做的想法来自这个网站 (LINK),它没有说明如何实现这一点。

【问题讨论】:

即使你将它们分开,实现__next__的那个也必须实现__iter__(返回自身)。 【参考方案1】:

听起来您混淆了 iteratorsiterables。 Iterables 有一个 __iter__ 方法,它返回一个迭代器。迭代器有一个__next__ 方法,该方法返回它们的下一个值或引发StopIteration。现在在 python 中,stated 迭代器也是可迭代对象(但反之亦然),iter(iterator) is iterator 所以迭代器itr 应该只从它的__iter__ 方法返回自身。

迭代器需要有一个 __iter__() 方法来返回迭代器对象本身,因此每个迭代器也是可迭代的,并且可以在接受其他可迭代的大多数地方使用

在代码中:

class MyIter:
   def __iter__(self):
       return self

   def __next__(self):
       # actual iterator logic

如果你想创建一个自定义迭代器类,最简单的方法是从collections.abc.Iterator 继承,你可以看到上面定义了__iter__(它也是collections.abc.Iterable 的子类)。那么你所需要的就是

class MyIter(collections.abc.Iterator):
    def __next__(self):
        ...

当然还有一种更简单的方法来制作迭代器,那就是使用生成器函数

def fib():
    a = 1
    b = 1
    yield a
    yield b
    while True:
        b, a = a + b, b
        yield b

list(itertools.takewhile(lambda x: x < 100, fib()))
# --> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

仅供参考,这是抽象迭代器和可迭代的(简化)代码

from abc import ABC, abstractmethod

class Iterable(ABC):

    @abstractmethod
    def __iter__(self):
        'Returns an instance of Iterator'
        pass

class Iterator(Iterable, ABC):

    @abstractmethod
    def __next__(self):
        'Return the next item from the iterator. When exhausted, raise StopIteration'
        pass

    # overrides Iterable.__iter__
    def __iter__(self):
        return self

    

【讨论】:

谢谢,但我需要澄清一下:如果我在一个类中定义__iter__,我会告诉解释器它是一个可迭代对象。当我在这个方法中返回 self 时,我返回的是实例而不是迭代器对象,对吧?但是根据文档,这应该是一个迭代器对象,这让我很困惑。 如果你定义了__iter__,那么这个对象就是iterable。如果您定义__next__,则该对象是一个迭代器。在 iterator 对象上,您应该设置 __iter__ 以返回对象本身,正如我所说,它是一个 iteratoriteratorsiterables,当迭代(例如 for 循环)时,会返回它们自己。【参考方案2】:

我想我现在已经掌握了这个概念,即使我不完全理解@FHTMitchell 文档中的段落。我遇到了一个关于如何分离这两种方法的示例,并想记录下来。

我发现的是一个非常basic tutorial,它清楚地区分了可迭代对象和迭代器(这是我困惑的原因)。

基本上,您首先将可迭代定义为一个单独的类:

class EvenNumbers:

    def __init__(self, max_):
        self.max = max_

    def __iter__(self):
        self.n = 0
        return EvenNumbersIterator(self)

__iter__ 方法只需要一个定义了__next__ 方法的对象。因此,您可以这样做:

class EvenNumbersIterator:

    def __init__(self, source):
        self.source = source       

    def __next__(self):
        if self.source.n <= self.source.max:
            result = 2 * self.source.n
            self.source.n += 1
            return result
        else:
            raise StopIteration

这将迭代器部分与可迭代类分开。现在,如果我在可迭代类中定义 __next__,我必须返回对实例本身的引用,因为它基本上一次完成 2 个工作。

【讨论】:

您的迭代器不是有效的迭代器(或至少违反约定并可能导致令人困惑的错误),因为迭代器也必须是可迭代的,如我的回答中所述。你要么需要继承collections.abc.Iterator,要么定义def __iter__(self): return self

以上是关于分离 __iter__ 和 __next__ 方法的主要内容,如果未能解决你的问题,请参考以下文章

一个列表实现__iter__和__next__方法的例子

python中的__iter__ __reversed__ __next__

__iter__ 和 __next__

__iter__和__next__实现迭代器协议

__iter__和__next__实现迭代器协议

十七. __next__和__iter__实现迭代器协议