如何在python中创建迭代函数(或迭代器对象)?
python中的迭代器对象符合迭代器协议,这基本上意味着它们提供了两种方法:__iter__()
和 __next__()
.在__iter__
返回迭代器对象,并隐式调用在循环的开始.该__next__()
方法返回下一个值,并在每个循环增量处隐式调用. __iter__()
当没有更多值返回时引发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,是一个非常好的介绍.
有四种方法可以构建迭代函数:
创建一个生成器(使用yield关键字)
使用生成器表达式(genexp)
创建一个迭代器(定义__iter__
和__next__
(或next
在Python 2.x中))
创建一个Python可以自己迭代的函数(定义__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()
; plain iterator(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
首先,itertools模块对于迭代器很有用的各种情况非常有用,但是这里只需要在python中创建一个迭代器:
让
那不是很酷吗?Yield可用于替换函数中的正常返回.它返回的对象是相同的,但它不是破坏状态和退出,而是为你想要执行下一次迭代时保存状态.以下是直接从itertools函数列表中提取的操作示例:
def count(n=0): while True: yield n n += 1
正如函数描述中所述(它是来自itertools模块的count()函数...),它产生一个迭代器,它返回以n开头的连续整数.
生成器表达式是另一种蠕虫(真棒蠕虫!).可以使用它们代替List Comprehension来节省内存(列表推导在内存中创建一个列表,如果没有分配给变量,在使用后会被销毁,但生成器表达式可以创建一个生成器对象...这是一种奇特的方式说Iterator).以下是生成器表达式定义的示例:
gen = (n for n in xrange(0,11))
这与上面的迭代器定义非常相似,只是整个范围预定在0到10之间.
我刚刚找到xrange()(我以前没见过它而感到惊讶......)并将它添加到上面的例子中. xrange()是range()的可迭代版本,其优点是不预先构建列表.如果你有一个巨大的数据集来迭代并且只有这么多的内存来完成它将是非常有用的.
我看到一些你做return self
在__iter__
.我只是想指出,__iter__
本身可以是一台发电机(从而消除了需要__next__
和提高StopIteration
例外)
class range: def __init__(self,a,b): self.a = a self.b = b def __iter__(self): i = self.a while i < self.b: yield i i+=1
当然,这里也可以直接生成一个生成器,但对于更复杂的类,它可能很有用.
这个问题是关于可迭代的对象,而不是迭代器.在Python中,序列也是可迭代的,因此制作可迭代类的一种方法是使其行为像序列,即给它__getitem__
和__len__
方法.我在Python 2和3上测试了这个.
class CustomRange: def __init__(self, low, high): self.low = low self.high = high def __getitem__(self, item): if item >= len(self): raise IndexError("CustomRange index out of range") return self.low + item def __len__(self): return self.high - self.low cr = CustomRange(0, 10) for i in cr: print(i)
对于复杂的对象,此页面上的所有答案都非常有用。但对于含有内置的迭代类型,属性那些像str
,list
,set
或dict
,或任何实现collections.Iterable
,你可以在你的类省略某些事情。
class Test(object): def __init__(self, string): self.string = string def __iter__(self): # since your string is already iterable return (ch for ch in self.string) # or simply return self.string.__iter__() # also return iter(self.string)
可以像这样使用:
for x in Test("abcde"): print(x) # prints # a # b # c # d # e