只有__iter__实现为generator-func,想使用next()

时间:2017-01-15 03:35:36

标签: python python-3.x iterator

我的课程__iter__定义如下:

class MyIterator:

    def __iter__(self):
        for value in self._iterator:
            if is_iterator(value):
                yield from value
            else:
                yield value

我想next(my_iterator)但我必须实施__next__才能这样做。但它会将这个简单的实现改变为相当复杂的实现 - 或者实际上我不知道如何实现它而不是将__iter__定义为生成器函数。

一般来说,如果__iter__被实现为没有生成器可能很难完成的生成器功能,那么如果我想使用__next__我该怎么办?

注意:显然,next(iter(my_iterator))有效,但我不想这样做。

3 个答案:

答案 0 :(得分:1)

如果您的类应该是迭代器,则不应将其__iter__方法实现为生成器函数。这使得类可迭代,但不是迭代器。迭代器的__iter__方法应该自行返回。

如果你真的希望你的类成为迭代器,请尝试这样的事情:

class MyIterator:
    def __init__(self, iterator):
        self._iterator = iterator
        self._subiterator = None

    def __iter__(self):
        return self

    def __next__(self):
        while True:
            if self._subiterator is None:
                value = next(self._iterator) # may raise StopIteration

                try:  # could test is_iterator(value) here for LBYL style code
                    self._subiterator = iter(value)
                except TypeError:
                    return value

            try:
                return next(self._subiterator)
            except StopIteraton:
                self._subiterator = None

next(self._iterator)来电可能会引发StopIteration,这是我故意不会抓到的。这个例外是我们完成迭代的信号,所以如果我们抓住了它,我们只需要再次提升它。

此代码使用“更容易请求宽恕而非权限”(EAFP)方法来检测迭代器中的可迭代项目。它只是尝试在每一个上调用iter并捕获将被提升的TypeError,如果它们不可迭代的话。如果您更喜欢坚持使用“Look Before You Leap”(LBYL)样式并使用is_iterator(由于它检查任何类型的可迭代,而不仅仅是迭代器)进行显式测试,您可以替换内部try

if is_iterator(value):
    return value
else:
    self._subiterator = iter(value)

在我的Python代码中,我通常更喜欢EAFP样式和LBYL样式,但有些情况下哪一个可以更好。其他时候,这只是风格问题。

答案 1 :(得分:0)

正如@BrenBarm评论的那样,明显的答案是通过保留next(iter(self))来返回next(self._iter_self)self._iter_self = iter(self)。我无法想出来。

答案 2 :(得分:0)

  • 迭代器是一个具有__next__方法的对象,该方法返回值,直到最终提升StopIteration
  • iterable是一个带有__iter__方法的对象,它返回一个迭代器。
  • 当函数或方法包含yield语句时,生成器是由python创建的特殊迭代。

在您的示例中,__iter__具有收益率,因此它是一个生成器。这意味着它返回另一个迭代,而不是迭代器。这就是为什么你必须做那个奇怪的next(iter(my_iterator))事情,而这不起作用,因为它每次都重新启动enumation。

如何最好地解决此问题取决于您如何使用此类。您可以改为创建生成器函数,然后根据需要使用iter生成迭代器:

import collections.abc

def is_iterator(i):
    return isinstance(i, collections.abc.Iterable)

def MyIterator(iterable):
    for value in iterable:
        if is_iterator(value):
            yield from value
        else:
            yield value

test_this = [1,2, [3, 4, 5], [6], [], 7, 'foo']
my_iterator = iter(MyIterator(test_this))
try:
    while True:
        print(next(my_iterator))
except StopIteration:
    pass

或者您可以实施__next__而不是__iter__。但是你不能使用yield并且必须在每次调用时返回一个值,直到外部迭代器完成并引发StopIteration

import collections.abc

class MyIterator:

    def __init__(self, iterable):
        self._iterator = iter(iterable)
        self._iterating = None

    def __next__(self):
        while True:
            if self._iterating is not None:
                try:
                    return next(self._iterating)
                except StopIteration:
                    self._iterating = None
            value = next(self._iterator)
            if isinstance(value, collections.abc.Iterable):
                self._iterating = iter(value)
            else:
                return value


test_this = [1,2, [3, 4, 5], [6], [], 7, 'foo']
my_iterator = MyIterator(test_this)
try:
    while True:
        print(next(my_iterator))
except StopIteration:
    pass