Vor*_*rac 2 python iterator iterable python-3.x
我正在尝试为Web资源(延迟获取的图像)实现可迭代的代理。
首先,我做到了(返回id,在生产中,这些将是图像缓冲区)
def iter(ids=[1,2,3]):
for id in ids:
yield id
Run Code Online (Sandbox Code Playgroud)
效果很好,但现在我需要保持状态。
我阅读了定义迭代器的四种方法。我判断迭代器协议是要走的路。跟随我的尝试和失败,以实现这一目标。
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)
Run Code Online (Sandbox Code Playgroud)
输出:
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>
Run Code Online (Sandbox Code Playgroud)
永远。
怎么了?
绝对感谢所有人!这对我来说都是新的,但是我正在学习新的有趣的东西。
您的__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
Run Code Online (Sandbox Code Playgroud)
请注意,生成器函数不应使用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>
Run Code Online (Sandbox Code Playgroud)
甲发生器对象是特定种类的迭代器,通过调用一个发生器函数创建一个,或通过使用发电机的表达。请注意,表示形式中的十六进制值不同,为两个调用创建了两个不同的对象。这是设计使然!可迭代产生迭代器,并且可以随意创建更多迭代器。这使您可以独立地遍历它们:
>>> test_it1 = test.__iter__()
>>> test_it1.__next__()
1
>>> test_it2 = test.__iter__()
>>> test_it2.__next__()
1
>>> test_it1.__next__()
2
Run Code Online (Sandbox Code Playgroud)
请注意,我调用__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
Run Code Online (Sandbox Code Playgroud)
这需要做更多的工作。它必须跟踪要产生的下一个价值是什么,以及我们是否已经提高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
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
您可以使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
Run Code Online (Sandbox Code Playgroud)
这与IteratorTest上面的原始类非常相似,但是TestIterator保留了对Test实例的引用。确实tuple_iterator也是这样。
关于命名约定的简短最后注释:我坚持使用self方法的第一个参数,因此绑定实例。为该参数使用不同的名称只会使与其他经验丰富的Python开发人员更难谈论您的代码。不要使用me,尽管它看起来多么可爱或简短。
(*)当然,除非您的目标是创建迭代器的迭代器(基本上是itertools.groupby()迭代器的工作,否则它是产生(object, group_iterator)元组的迭代器,但我离题了)。
| 归档时间: |
|
| 查看次数: |
503 次 |
| 最近记录: |