您的__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,只需从函数返回即可。在
上面的类是iterable。Iterables只有一个__iter__方法,和没有__next__方法。当调用__iter__时,Iterables生成一个迭代器:
Iterable->;(调用__iter__)->;迭代器
在上面的例子中,因为Test.__iter__是一个生成器函数,所以每次调用它时它都会创建一个新对象:
^{pr2}$
生成器对象是一种特定类型的迭代器,通过调用生成器函数或使用生成器表达式创建。注意,表示中的十六进制值不同,为这两个调用创建了两个不同的对象。这是故意的!Iterables生成迭代器,并可以随意创建更多的迭代器。这使您可以独立地循环它们:>>> test_it1 = test.__iter__()
>>> test_it1.__next__()
1
>>> test_it2 = test.__iter__()
>>> test_it2.__next__()
1
>>> test_it1.__next__()
2
请注意,我对test.__iter__()返回的对象调用了__next__(),迭代器,而不是{}本身,它没有这个方法,因为它只是一个iterable,不是迭代器。在
迭代器还有一个__iter__方法,它总是必须返回self,因为它们是它们自己的迭代器。正是__next__方法使它们成为迭代器,__next__的作业将被反复调用,直到它引发{}。在引发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中实现迭代器,那么钩子的名称就略有不同。相反,您可以使用^{}和^{}函数,或者只使用语法中的对象或接受iterable的函数调用。在
for循环就是这样的语法。当您使用for循环时,Python使用(道德上等价的)对对象调用__iter__(),然后对结果迭代器对象调用__next__()来获取每个值。如果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处的GET_ITER操作码调用test.__iter__(),FOR_ITER使用结果迭代器上的__next__继续循环(执行STORE_NAME将{}设置为下一个值,然后跳回位置4),直到StopIteration被引发。一旦发生这种情况,它将跳到位置10结束循环。在
如果您想更多地了解迭代器和iterables之间的区别,请看一下Python标准类型,看看在它们上使用iter()和{}时会发生什么。像列表或元组:>>> foo = (42, 81, 17, 111)
>>> next(foo) # foo is a tuple, not an iterator
Traceback (most recent call last):
File "", line 1, in
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
>>> iter(t_it) # it returns itself
>>> 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 "", line 1, in
StopIteration
>>> next(t_it) # an *stay* done
Traceback (most recent call last):
File "", line 1, in
StopIteration
>>> foo # but foo itself is still there
(42, 81, 17, 111)
您可以使iterable的Test也返回一个自定义迭代器类实例(而不是havi跳出)ng 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
这很像上面原始的IteratorTest类,但是TestIterator保留了对Test实例的引用。这就是tuple_iterator的工作原理。在
关于命名约定的最后一点简短说明:我坚持使用self作为方法的第一个参数,因此绑定实例。对该参数使用不同的名称只会增加与其他经验丰富的Python开发人员谈论代码的难度。不要使用me,不管它看起来多么可爱或短。在
当然,除非您的目标是创建一个迭代器的迭代器(这基本上就是^{} iterator所做的,它是一个生成(object, group_iterator)元组的迭代器,但我离题了)。在