我正在尝试为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>
永远。
怎么了?
感谢所有人!这对我来说都是新的,但是我正在学习新的有趣的东西。
答案 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)