如何在python中创建一个迭代函数(或迭代器对象)?
python中的迭代器对象符合迭代器协议,这基本上意味着它们提供了两种方法:__iter__() 和 __next__()。
__iter__()
__next__()
在__iter__返回迭代器对象,并在循环开始时隐式调用。
__iter__
该__next__()方法返回下一个值,并在每次循环增量时隐式调用。当没有更多值要返回时,此方法将引发StopIteration异常,该异常由循环构造以停止迭代的方式隐式捕获。
StopIteration
这是一个简单的计数器示例:
class Counter: def __init__(self, low, high): self.current = low - 1 self.high = high def __iter__(self): return self def __next__(self): # Python 2: def next(self) self.current += 1 if self.current < self.high: return self.current raise StopIteration for c in Counter(3, 9): print(c)
这将打印:
3 4 5 6 7 8
如上一个答案所述,使用生成器编写起来更容易:
def counter(low, high): current = low while current < high: yield current current += 1 for c in counter(3, 9): print(c)
打印的输出将相同。在内部,生成器对象支持迭代器协议,并且执行与类Counter大致相似的操作。
David Mertz的文章Iterators和Simple Generators是很好的介绍。
David Mertz
Iterators
Simple Generators
有四种方法来构建迭代函数:
yield
genexp
__next__
__getitem__
例子:
# generator def uc_gen(text): for char in text: yield char.upper() # generator expression def uc_genexp(text): return (char.upper() for char in text) # iterator protocol class uc_iter(): def __init__(self, text): self.text = text self.index = 0 def __iter__(self): return self def __next__(self): try: result = self.text[self.index].upper() except IndexError: raise StopIteration self.index += 1 return result # getitem method class uc_getitem(): def __init__(self, text): self.text = text def __getitem__(self, index): result = self.text[index].upper() return result
要查看所有四个实际方法:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem: for ch in iterator('abcde'): print ch, print
结果是:
A B C D E A B C D E A B C D E A B C D E
注意事项:
两种生成器类型(uc_gen和uc_genexp)不能为reversed(); 普通的迭代器(uc_iter)需要使用__reversed__magic方法(该方法必须返回一个向后的新迭代器);并且getitem iteratable(uc_getitem)必须具有__len__魔术方法:
uc_gen和uc_genexp
reversed()
uc_iter
__reversed__magic
getitem iteratable
uc_getitem
__len__
# for uc_iter def __reversed__(self): return reversed(self.text) # for uc_getitem def __len__(self) return len(self.text)
为了回答Panic上校关于无限懒惰求值的迭代器的第二个问题,以下是使用上述四种方法中的每一个的示例:
# generator def even_gen(): result = 0 while True: yield result result += 2 # generator expression def even_genexp(): return (num for num in even_gen()) # or even_iter or even_getitem # not much value under these circumstances # iterator protocol class even_iter(): def __init__(self): self.value = 0 def __iter__(self): return self def __next__(self): next_value = self.value self.value += 2 return next_value # getitem method class even_getitem(): def __getitem__(self, index): return index * 2 import random for iterator in even_gen, even_genexp, even_iter, even_getitem: limit = random.randint(15, 30) count = 0 for even in iterator(): print even, count += 1 if count >= limit: break print
结果(至少在我的示例运行中):
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
如何选择使用哪一个?这主要是一个品味问题。我最常看到的两种方法是生成器和迭代器协议,以及混合方法(__iter__返回生成器)。
生成器表达式可用于替换列表推导(它们很懒,因此可以节省资源)。
如果需要与早期的Python 2.x版本兼容,请使用__getitem__。