构build一个基本的Python迭代器
如何在Python中创build一个迭代函数(或迭代器对象)?
python中的迭代器对象符合迭代器协议,这意味着它们提供了两个方法: __iter__()
和next()
。 __iter__
返回迭代器对象,并在循环开始时隐式调用。 next()
方法返回下一个值,并在每个循环增量中隐式调用。 next()
会在没有更多值返回时引发StopIterationexception,这是通过循环结构隐式捕获来停止迭代的。
这是一个简单的计数器例子:
class Counter: def __init__(self, low, high): self.current = low self.high = high def __iter__(self): return self def next(self): # Python 3: def __next__(self) if self.current > self.high: raise StopIteration else: self.current += 1 return self.current - 1 for c in Counter(3, 8): 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, 8): print c
打印的输出将是相同的。 在引擎盖下,生成器对象支持迭代器协议,并且做类似于类Counter的东西。
David Mertz的文章“ 迭代器和简单生成器 ”是一个很好的介绍。
有四种方法来构build迭代函数:
- 创build一个生成器(使用yield关键字 )
- 使用生成器expression式( genexp )
- 创build一个迭代器(定义
__iter__
和__next__
(或Python 2.x中的next
)) - 创build一个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
其结果是:
ABCDE ABCDE ABCDE ABCDE
注意 :
两个生成器types( uc_gen
和uc_genexp
)不能reversed()
; 普通迭代器( uc_iter
)将需要__reversed__
魔术方法(它必须返回一个新的迭代器向后)。 而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
其结果(至less对我的样品运行):
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中创build一个迭代器:
产量
这不是很酷吗? 产量可以用来取代函数中的正常返回 。 它返回的对象是一样的,但不是销毁状态和退出,而是保存当你想要执行下一个迭代的状态。 下面是一个直接从itertools函数列表中取出的例子:
def count(n=0): while True: yield n n += 1
正如函数描述中所述(它是itertools模块的count()函数),它将生成一个迭代器,它返回从n开始的连续整数。
发生器expression式是一个完整的蠕虫(真棒蠕虫!)。 它们可以用来代替列表理解来节省内存(列表parsing在内存中创build一个列表,在使用后会被销毁,如果没有分配给一个variables,但是生成器expression式可以创build一个生成器对象…这是一个奇特的方式说迭代器)。 这是一个生成器expression式定义的例子:
gen = (n for n in xrange(0,11))
这与我们上面的迭代器定义非常相似,除了全部范围预定在0和10之间。
我刚刚发现xrange() (惊讶我以前没见过…),并将其添加到上面的示例。 xrange()是range()的可迭代版本,其优点是不会预build列表。 如果你有一个巨大的数据集来迭代,只有这么多的内存来完成,那将是非常有用的。
我看到你们中的一些人在__iter__
return self
。 我只是想指出, __iter__
本身可以是一个生成器(从而消除__next__
和提高StopIteration
exception的需要)
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上testing了这个。
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)
这是一个没有yield
的迭代函数。 它使用了iter
函数和一个闭包,它将它的状态保存在python 2的封闭范围内的一个可变( list
)中。
def count(low, high): counter = [0] def tmp(): val = low + counter[0] if val < high: counter[0] += 1 return val return None return iter(tmp, None)
对于Python 3,封闭状态在封闭范围内保持不变, nonlocal
局部范围在本地范围内用于更新状态variables。
def count(low, high): counter = 0 def tmp(): nonlocal counter val = low + counter if val < high: counter += 1 return val return None return iter(tmp, None)
testing;
for i in count(1,10): print(i) 1 2 3 4 5 6 7 8 9