python3中的可迭代类

虚度

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

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

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

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

我阅读了定义迭代器的四种方法我判断迭代器协议是要走的路。请按照我的尝试和失败来实现。

class Test:
    def __init__(self, ids):
         self.ids = ids
    def __iter__(self):
        return self
    def __next__(self):
        for id in self.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>

永远。

怎么了?


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

马丁·彼得斯(Martijn Pieters)

您的__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__调用生成一个迭代器

迭代->(调用__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__必须反复调用的工作,直到它提高为止StopIterationStopIteration引发之前,每个调用应返回下一个值。迭代器完成(已引发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)(i for i in ids)正在创建其他迭代器以将__next__调用委派给时。这有点作弊,将迭代器的状态隐藏在现成的标准库对象中。

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

for循环是这样的语法。当您使用for循环时,Python使用(等同__iter__()道德的方法)先调用对象,然后再调用__next__()生成的迭代器对象以获取每个值。如果您反汇编Python字节码,则可以看到以下内容

>>> 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

GET_ITER位置2操作码调用test.__iter__(),并在生成的迭代器上FOR_ITER使用__next__它来保持循环(执行STORE_NAME以设置t为下一个值,然后跳回到位置4),直到StopIteration引发。一旦发生这种情况,它将跳到位置10结束循环。

如果你想玩更多的迭代器和iterables之间的差别,看看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)

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

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()迭代器的工作,否则它是产生(object, group_iterator)元组的迭代器,但我离题了)。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章