如何构建基本的迭代器?
我该如何在Python中创建一个迭代器呢?
举个例子,假设我有一个类,它的实例在逻辑上“包含”一些值:
class Example:
def __init__(self, values):
self.values = values
我希望能够写出像这样的代码:
e = Example([1, 2, 3])
# Each time through the loop, expose one of the values from e.values
for value in e:
print("The example object contains", value)
更一般来说,这个迭代器应该能够控制值的来源,甚至可以实时计算这些值(而不是仅仅考虑实例的某个特定属性)。
10 个回答
我看到有些人在__iter__
里面使用return self
。我想提醒一下,其实__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
当然,在这里你也可以直接创建一个生成器,但对于一些更复杂的类,这样做可能会更有用。
有四种方法可以创建一个迭代函数:
- 创建一个生成器(使用 yield 关键字)
- 使用生成器表达式 (genexp)
- 创建一个迭代器(定义
__iter__
和__next__
(或者在 Python 2.x 中使用next
)) - 创建一个可以被 Python 自己迭代的类(定义
__getitem__
)
示例:
# generator
def uc_gen(text):
for char in text.upper():
yield char
# generator expression
def uc_genexp(text):
return (char for char in text.upper())
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text.upper()
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text.upper()
def __getitem__(self, index):
return self.text[index]
要查看这四种方法的实际效果:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print(ch, end=' ')
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__
这个魔法方法(根据文档,这个方法必须返回一个新的迭代器,但返回 self
也是可以的(至少在 CPython 中是这样));而可通过 getitem
迭代的对象(uc_getitem
)必须有 __len__
这个魔法方法:
# for uc_iter we add __reversed__ and update __next__
def __reversed__(self):
self.index = -1
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += -1 if self.index < 0 else +1
return result
# for uc_getitem
def __len__(self)
return len(self.text)
为了回答 Colonel 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__
。
在Python中,迭代器对象遵循迭代器协议,这基本上意味着它们提供了两个方法:__iter__()
和 __next__()
。
__iter__
方法返回迭代器对象,并在循环开始时自动调用。__next__()
方法返回下一个值,并在每次循环增加时自动调用。当没有更多值可以返回时,这个方法会抛出一个叫做 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 的文章,迭代器和简单生成器,是一个很好的入门介绍。