您的__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,只需从函数返回即可。
上面的类是一个可迭代的。 Iterables 只有__iter__ 方法,没有__next__ 方法。当 __iter__ 被调用时,Iterables 会产生一个 iterator:
Iterable -> (调用__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>
生成器对象 是一种特定类型的迭代器,通过调用生成器函数或使用生成器表达式创建。请注意,表示中的十六进制值不同,为两个调用创建了两个不同的对象。这是设计使然! Iterables 产生迭代器,并且可以随意创建更多。这使您可以独立地循环它们:
>>> test_it1 = test.__iter__()
>>> test_it1.__next__()
1
>>> test_it2 = test.__iter__()
>>> test_it2.__next__()
1
>>> test_it1.__next__()
2
请注意,我在test.__iter__()(迭代器)返回的对象上调用了__next__(),而不是在test 本身上调用了__next__(),它没有那个方法,因为它只是一个可迭代的,而不是一个迭代器。
迭代器还有一个__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。这里的其他回答者使用了看似更简单的方法,但实际上这些方法涉及让 else 完成所有艰苦的工作。当您使用 iter(self.ids) 或 (i for i in ids) 时,您正在创建一个不同的迭代器来委托 __next__ 调用。这有点作弊,将迭代器的状态隐藏在现成的标准库对象中。
您通常不会在 Python 代码中看到任何调用 __iter__ 或 __next__ 的内容,因为这两个方法只是您可以在 Python 类中实现的钩子;如果您要在 C API 中实现迭代器,那么钩子名称会略有不同。相反,您可以使用 iter() 和 next() 函数,或者仅使用语法中的对象或接受可迭代的函数调用。
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 将t 设置为下一个值,然后跳转回位置 4),直到出现StopIteration。一旦发生这种情况,它将跳转到位置 10 以结束循环。
如果您想更多地了解迭代器和可迭代对象之间的区别,请查看 Python 标准类型,看看在对它们使用 iter() 和 next() 时会发生什么。 Like 列表或元组:
>>> 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) 元组的迭代器,但我离题了)。