python3中的可迭代类

时间:2019-05-21 11:10:37

标签: python python-3.x iterator iterable

我正在尝试为Web资源(延迟获取的图像)实现可迭代的代理。

首先,我做到了(返回ID,在生产中,这些将是图像缓冲区)

def iter(ids=[1,2,3]):
    for id in ids:
        yield id

效果很好,但现在我需要保持状态。

我读了the four ways to define iterators。我判断迭代器协议是要走的路。请按照我的尝试和失败来实现。

class Test:
    def __init__(me, ids):
         me.ids = ids
    def __iter__(me):
        return me
    def __next__(me):
        for id in me.ids:
            yield id
        raise StopIteration

test = Test([1,2,3])
for t in test:
    print('new value', t)

输出:

new value <generator object Test.__next__ at 0x7f9c46ed1750>
new value <generator object Test.__next__ at 0x7f9c46ed1660>
new value <generator object Test.__next__ at 0x7f9c46ed1750>
new value <generator object Test.__next__ at 0x7f9c46ed1660>
new value <generator object Test.__next__ at 0x7f9c46ed1750>

永远。

怎么了?


感谢所有人!这对我来说都是新的,但是我正在学习新的有趣的东西。

4 个答案:

答案 0 :(得分:7)

您的__next__方法使用yield,这使其成为生成器函数。生成器函数在调用时会返回一个新的迭代器。

但是__next__方法是迭代器接口的一部分。它本身不应该是迭代器。 __next__应该返回下一个值,而不是返回所有值的(*)

由于您要创建一个 iterable ,因此可以在此处将 __iter__ 生成为该生成器:

class Test:
    def __init__(self, ids):
         self.ids = ids
    def __iter__(self):
        for id in self.ids:
            yield id

请注意,生成器函数不应使用raise StopIteration ,仅从函数返回即可。

以上类是可迭代的。可迭代对象只有一种__iter__方法,,没有__next__方法。调用__iter__时,可迭代对象产生 iterator

可迭代->(调用__iter__)-> 迭代器

在上面的示例中,由于Test.__iter__是一个生成器函数,因此每次我们调用它时都会创建一个新对象:

>>> test = Test([1,2,3])
>>> test.__iter__()  # create an iterator
<generator object Test.__iter__ at 0x111e85660>
>>> test.__iter__()
<generator object Test.__iter__ at 0x111e85740>

生成器对象是一种特定的迭代器,通过调用生成器函数或使用生成器表达式创建。请注意,表示形式中的十六进制值不同,为两个调用创建了两个不同的对象。这是设计使然!可迭代产生迭代器,并且可以随意创建更多迭代器。这使您可以独立地遍历它们:

>>> test_it1 = test.__iter__()
>>> test_it1.__next__()
1
>>> test_it2 = test.__iter__()
>>> test_it2.__next__()
1
>>> test_it1.__next__()
2

请注意,我在__next__()返回的对象(迭代器)上调用了test.__iter__(),而不是在test本身上调用了__iter__,因为它只是一个可迭代的,而不是迭代器。

迭代器还具有self方法,该方法始终必须返回__next__,因为它们是它们自己的迭代器。正是__next__方法使它们成为迭代器,StopIteration的工作将被重复调用,直到引发StopIteration。在引发StopIteration之前,每个调用应返回下一个值。迭代器完成(已引发StopIteration)后,它就意味着始终会引发StopIteration。迭代器只能使用一次,除非它们是无限的(永远不要提高__next__,并且每次调用class IteratorTest: def __init__(self, ids): self.ids = ids self.nextpos = 0 def __iter__(self): return self def __next__(self): if self.ids is None or self.nextpos >= len(self.ids): # we are done self.ids = None raise StopIteration value = self.ids[self.nextpos] self.nextpos += 1 return value 时都只会产生值)。

这是一个迭代器:

StopIteration

这还需要做更多的工作;它必须跟踪将要产生的下一个值是什么,以及是否已经提高iter(self.ids)。这里的其他回答者都使用了看似更简单的方法,但实际上是让 else 来完成所有艰苦的工作。当您使用(i for i in ids)__next__时,您正在创建一个不同的迭代器来委托__iter__的调用。有点作弊,将迭代器的状态隐藏在现成的标准库对象中。

您通常不会在Python代码中看到任何调用__next__for的东西,因为这两种方法只是您可以在Python类中实现的钩子;如果要在C API中实现迭代器,则挂钩名称略有不同。相反,您可以使用iter()next()函数,或者仅在语法或接受可迭代对象的函数调用中使用对象。

for循环就是这样的语法。当您使用__iter__()循环时,Python使用(等同于道德的方法)在对象上调用__next__(),然后在生成的迭代器对象上调用>>> from dis import dis >>> dis("for t in test: pass") 1 0 LOAD_NAME 0 (test) 2 GET_ITER >> 4 FOR_ITER 4 (to 10) 6 STORE_NAME 1 (t) 8 JUMP_ABSOLUTE 4 >> 10 LOAD_CONST 0 (None) 12 RETURN_VALUE 以获取每个值。如果您disassemble the Python bytecode

GET_ITER

位置2的test.__iter__()操作码调用FOR_ITER__next__在结果迭代器上使用STORE_NAME来保持循环(执行t来设置{{ 1}}移至下一个值,然后跳回到位置4),直到提高StopIteration。一旦发生这种情况,它将跳到位置10结束循环。

如果您想更多地利用迭代器和可迭代器之间的区别,请看一下Python标准类型,看看在它们上使用iter()next()时会发生什么。像列表或元组一样:

>>> foo = (42, 81, 17, 111)
>>> next(foo)  # foo is a tuple, not an iterator
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object is not an iterator
>>> t_it = iter(foo)  # so use iter() to create one from the tuple
>>> t_it   # here is an iterator object for our foo tuple
<tuple_iterator object at 0x111e9af70>
>>> iter(t_it)  # it returns itself
<tuple_iterator object at 0x111e9af70>
>>> iter(t_it) is t_it  # really, it returns itself, not a new object
True
>>> next(t_it)  # we can get values from it, one by one
42
>>> next(t_it)  # another one
81
>>> next(t_it)  # yet another one
17
>>> next(t_it)  # this is getting boring..
111
>>> next(t_it)  # and now we are done
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> next(t_it)  # an *stay* done
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> foo  # but foo itself is still there
(42, 81, 17, 111)

您可以使Test成为可迭代的,也返回自定义迭代器类实例(并且不要通过让生成器函数为我们创建迭代器来解决问题):

class Test:
    def __init__(self, ids):
        self.ids = ids
    def __iter__(self):
        return TestIterator(self)

class TestIterator:
    def __init__(self, test):
        self.test = test
    def __iter__(self):
        return self
def __next__(self):
    if self.test is None or self.nextpos >= len(self.test.ids):
        # we are done
        self.test = None
        raise StopIteration
    value = self.test.ids[self.nextpos]
    self.nextpos += 1
    return value

这与上面的原始IteratorTest类非常相似,但是TestIterator保留了对Test实例的引用。 tuple_iterator确实也是如此。

关于命名约定的简短最后注释:我坚持使用self作为方法的第一个参数,因此是绑定实例。为该参数使用不同的名称只会使与其他经验丰富的Python开发人员更难谈论您的代码。不要使用me,尽管它看起来多么可爱或简短。


(*)当然,除非您的目标是创建迭代器的迭代器(基本上是itertools.groupby() iterator的工作,否则它是生成(object, group_iterator)元组的迭代器,但我离题了。

答案 1 :(得分:1)

我不清楚您到底想达到什么目的,但是如果您真的想使用实例属性,可以将输入转换为生成器,然后对其进行迭代。但是,正如我说的那样,这感觉很奇怪,我认为您实际上并不需要这样的设置。

class Test:
    def __init__(self, ids):
         self.ids = iter(ids)
    def __iter__(self):
        return self
    def __next__(self):
        return next(self.ids)

test = Test([1,2,3])
for t in test:
    print('new value', t)

答案 2 :(得分:0)

__next__函数应该返回迭代器提供的下一个值。由于您在实现中使用了yield,因此该函数将返回生成器,这就是您所获得的。

您需要明确说明您想让Test是可迭代的还是迭代器。如果它是可迭代的,它将能够为迭代器提供__iter__。如果它是一个迭代器,它将能够为新元素提供__next__。迭代器通常可以通过在__iter__中返回自身来作为可迭代的工作。 Martijn's answer显示您可能想要的内容。但是,如果您想举一个示例,说明如何具体实现__next__(通过显式地使Test成为迭代器),则可能是这样的:

class Test:
    def __init__(self, ids):
        self.ids = ids
        self.idx = 0
    def __iter__(self):
        return self
    def __next__(self):
        if self.idx >= len(self.ids):
            raise StopIteration
        else:
            self.idx += 1
            return self.ids[self.idx - 1]

test = Test([1,2,3])
for t in test:
    print('new value', t)

答案 3 :(得分:0)

最简单的解决方案是使用__iter__并将迭代器返回到主列表:

class Test:
    def __init__(self, ids):
         self.ids = ids
    def __iter__(self):
        return iter(self.ids)

test = Test([1,2,3])
for t in test:
    print('new value', t)

作为更新,对于延迟加载,您可以将迭代器返回给生成器:

    def __iter__(self):
        return iter(load_file(id) for id in self.ids)