我正在尝试为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>
永远。
怎么了?
绝对感谢所有人!这对我来说都是新的,但是我正在学习新的有趣的东西。
您的__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__
必须反复调用的工作,直到它提高为止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)
或(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)
您可以使Test
iterable,也返回自定义迭代器类实例(并且不要通过让生成器函数为我们创建迭代器来解决):
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] 删除。
我来说两句