Build a Basic Python Iterator
如何在Python中创建迭代函数(或迭代器对象)?
python中的迭代器对象符合迭代器协议,这基本上意味着它们提供了两种方法:
下面是一个简单的计数器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 |
这将打印:
1 2 3 4 5 6 | 3 4 5 6 7 8 |
如前一个答案所述,使用生成器更容易编写:
1 2 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 |
打印输出将相同。在引擎盖下,生成器对象支持迭代器协议,并执行与类计数器大致相似的操作。
DavidMertz的文章,迭代器和简单的生成器,是一个很好的介绍。
构建迭代函数有四种方法:
- 创建生成器(使用yield关键字)
- 使用生成器表达式(genexp)
- 创建迭代器(在python 2.x中定义
__iter__ 和__next__ (或next ) - 创建一个类,python可以自己迭代(定义
__getitem__ )
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | # 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 |
要查看所有四种方法的作用:
1 2 3 4 | for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem: for ch in iterator('abcde'): print ch, |
结果是:
1 2 3 4 | A B C D E A B C D E A B C D E A B C D E |
注:
两种生成器类型(
1 2 3 4 5 6 7 | # for uc_iter def __reversed__(self): return reversed(self.text) # for uc_getitem def __len__(self) return len(self.text) |
为了回答关于一个无限的延迟评估迭代器的第二个问题,下面是这些例子,使用上述四种方法中的每一种:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | # 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 |
结果是(至少对于我的示例运行):
1 2 3 4 | 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
那不酷吗?yield可用于替换函数中的正常返回。它返回的对象是相同的,但它不会破坏状态并退出,而是在您想要执行下一个迭代时保存状态。下面是一个直接从ITertools函数列表中提取的实际IT示例:
1 2 3 4 | def count(n=0): while True: yield n n += 1 |
如函数描述中所述(它是itertools模块中的count()函数…),它生成一个迭代器,返回以n开头的连续整数。
生成器表达式是一整罐其他蠕虫(可怕的蠕虫!)。它们可以用来代替列表理解来保存内存(列表理解在内存中创建一个列表,如果未分配给变量,则在使用后将其销毁,但生成器表达式可以创建生成器对象…这是一种奇特的迭代器的表达方式)。以下是生成器表达式定义的示例:
1 | gen = (n for n in xrange(0,11)) |
这与上面的迭代器定义非常相似,只是整个范围预先确定为0到10之间。
我刚找到xrange()(很惊讶我以前没见过它…)并将其添加到上面的示例中。xrange()是range()的一个可迭代版本,它具有不预先构建列表的优点。如果您有一个巨大的数据集进行迭代,并且只有那么多的内存来进行迭代,那么这将非常有用。
我看到你们中的一些人在
1 2 3 4 5 6 7 8 9 | 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 |
当然,这里我们也可以直接生成一个生成器,但是对于更复杂的类来说,它是有用的。
这个问题是关于可Iterable对象的,而不是关于迭代器的。在python中,序列也是可重写的,因此使一个可重写类的一种方法是使它表现得像一个序列,即给它提供
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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) |
这个页面上的所有答案对于一个复杂的对象都非常有用。但是对于那些包含内置的可Itable类型作为属性的类型,如
1 2 3 4 5 6 7 | class Test(object): def __init__(self, string): self.string = string def __iter__(self): # since your string is already iterable return (ch for ch in string) |
它的用途如下:
1 2 3 4 5 6 7 8 9 | for x in Test("abcde"): print(x) # prints # a # b # c # d # e |
这是一个不带
1 2 3 4 5 6 7 8 9 | 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,闭包状态在封闭范围内保持不变,并且在本地范围内使用
1 2 3 4 5 6 7 8 9 10 | 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) |
试验;
1 2 3 4 5 6 7 8 9 10 11 | for i in count(1,10): print(i) 1 2 3 4 5 6 7 8 9 |
如果你想找一些简短的东西,也许对你来说就足够了:
1 2 3 4 5 6 | class A(object): def __init__(self, l): self.data = l def __iter__(self): return iter(self.data) |
使用示例:
1 2 3 4 | In [3]: a = A([2,3,4]) In [4]: [i for i in a] Out[4]: [2, 3, 4] |
受Matt Gregory答案的启发,这里有一个更复杂的迭代器,它将返回a,b,…,z,a a,a b,…,z z,a a a,a a b,…,zzy,z z z
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class AlphaCounter: def __init__(self, low, high): self.current = low self.high = high def __iter__(self): return self def __next__(self): # Python 3: def __next__(self) alpha = ' abcdefghijklmnopqrstuvwxyz' n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))]) n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))]) if n_current > n_high: raise StopIteration else: increment = True ret = '' for x in self.current[::-1]: if 'z' == x: if increment: ret += 'a' else: ret += 'z' else: if increment: ret += alpha[alpha.find(x)+1] increment = False else: ret += x if increment: ret += 'a' tmp = self.current self.current = ret[::-1] return tmp for c in AlphaCounter('a', 'zzz'): print(c) |