我试图实现一个web资源的iterable代理(惰性地获取图像)。
首先,我做了(返回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)
Output:
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
,这使它成为一个生成器函数。生成器函数在调用时返回一个新的迭代器。
。。。
Because you wanted to create an iterable, you can just make __next__
the generator here:
class Test:
def __init__(self, ids):
self.ids = ids
def __iter__(self):
for id in self.ids:
yield id
请注意,生成器函数不应该使用
__next__
,只需从函数返回就可以了。。。当调用
__iter__
时,Iterables生成迭代器:Iterable -> (call
raise StopIteration
) -> IteratorIn the above example, because
__iter__
is a generator function, it creates a new object each time we call it:>>> test = Test([1,2,3])
>>> test.__iter__() # create an iterator
<generator object Test.__iter__ at 0x111e85660>
>>> test.__iter__()
<generator object Test.__iter__ at 0x111e85740>
。。!Iterables生成迭代器,可以随意创建更多迭代器。这使您可以独立循环它们:
>>> test_it1 = test.__iter__()
>>> test_it1.__next__()
1
>>> test_it2 = test.__iter__()
>>> test_it2.__next__()
1
>>> test_it1.__next__()
2
注意,我对
__next__
返回的对象调用了__iter__
,迭代器,而不是__iter__
本身,它没有那个方法,因为它只是一个iterable,而不是迭代器。迭代器还有一个
Test.__iter__
方法,它总是必须返回__next__()
,因为它们是自己的迭代器。。在引发test.__iter__()
之前,每个调用都应返回下一个值。。。So this is an iterator:
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
。这里的其他回答者使用了看起来更简单的方法,但实际上这些方法包括让其他事情做所有的艰苦工作。当您使用
test
或__iter__
时,您正在创建一个不同的迭代器来将self
调用委托给。。在Python代码中通常看不到调用
__next__
或__next__
的任何东西,因为这两个方法只是可以在Python类中实现的钩子;如果要在C API中实现迭代器,则钩子名称略有不同。相反,您可以使用StopIteration
和StopIteration
函数,或者只使用语法中的对象或接受iterable的函数调用。。。You can see this if you disassemble the Python bytecode:
>>> 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
位置2的
StopIteration
操作码调用StopIteration
,StopIteration
使用结果迭代器上的__next__
来保持循环(执行StopIteration
将iter(self.ids)
设置为下一个值,然后跳回位置4),直到(i for i in ids)
被提升。。如果您想更多地了解迭代器和iterables之间的区别,请查看Python标准类型,并查看在它们上使用
__next__
和__iter__
时会发生什么。如列表或元组:>>> 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)
您可以使iterable
__next__
也返回一个自定义迭代器类实例(而不是让generator函数为我们创建迭代器):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
。。
。对该参数使用不同的名称只会增加与其他有经验的Python开发人员讨论代码的难度。。
(*)当然,除非您的目标是创建迭代器的迭代器(这基本上是
iter()
iterator所做的,它是生成next()
元组的迭代器,但我偏离了方向)。
关于python - python3中的可迭代类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56237021/