当扫描内存中放不下的数据集时,我们需要找到一种惰性获取数据项的方式,每次“取出”1个。
这就是迭代器相对于普通可迭代对象的优势:节省内存
探索可迭代对象
l = ['apple', 'orange', 'pear'] #列表就是一个可迭代对象
在python中,很多内建的数据类型都是可迭代对象,如列表,字符串,元组,字典,集合等
如果要自己构建一个可迭代的数据类型,只需要实现一个__getitem__()方法。
接下来实现一个Sentence类,传入一个以空格分隔的英文句子,迭代句子中的单词。
class Sentence:
def __init__(self, text):
self.words = text.split()
def __getitem__(self, index):
return self.words[index]
现在测试一下这个Sentence类
>>> sen = Sentence('hello my world my name is Alfred')
>>> sen
<__main__.Sentence object at 0x02CF5F30>
>>> sen.words
['hello', 'my', 'world', 'my', 'name', 'is', 'Alfred']
>>> sen[3]
'my'
>>> for i in sen:
print(i)
hello
my
world
my
name
is
Alfred
可迭代对象与迭代器的对比
刚才我利用__getitem__()构建了一个可迭代对象sen,
那么如果我要构建一个迭代器,应该使用的魔法方法是__next__()和__iter__()
接下来利用__next__()和__iter__()构建一个Sentence2类。
class Sentence2:
def __init__(self, text):
self.words = text.split()
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
同样地,现在来测试一下这个Sentence2类
>>> sen2 = Sentence2('hello my world my name is Alfred')
>>> sen2.words
['hello', 'my', 'world', 'my', 'name', 'is', 'Alfred']
>>> sen2[5]
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
sen2[5]
TypeError: 'Sentence2' object does not support indexing
>>> for i in sen2:
print(i)
hello
my
world
my
name
is
Alfred
>>>
sen2的索引操作引发了TypeError异常,然而从for循环中可以看出,sen2依然是可迭代的。其实这里还有一个很神奇的现象,如果对sen2进行第二次for循环,会发现什么都得不到,sen2里面是空的。
>>> for i in sen2:
print(i)
>>>
实际上,当我们迭代一个对象x的时候,解释器会自动调用内置的iter(x)函数。它的作用有3个:
(1)检查对象是否实现了__iter__()方法,如果实现了就调用它,返回一个迭代器。
(2)如果没有实现__iter__(),就检查是否实现了__getitem__()方法,Python会自动创建一个迭代器,并从索引0开始获取元素。
(3)如果2个方法都没有实现,那么就只能抛出TypeError异常了“x object is not iterable”
为什么在上述的Sen2实例中,进行第二次迭代的时候,Sen2会没有元素可以迭代呢?
这是由于__iter__()方法使其自身成为了迭代器,当迭代的时候,next()方法会不断地返回序列中的下一个元素,最终导致耗尽了其自身的元素,然后抛出StopIteration异常。
为了让迭代器可以一直迭代,iter()方法应该是返回一个迭代器,而不是让其自身称为一个迭代器
所以,迭代器更准确的实现方式应该是只实现一个__iter__()方法,而__next__()方法应该封装在另一个类中,该类表示了迭代器的内部状态:
譬如如下Sentence3类:
class Sentence3:
def __init__(self, text):
self.words = text.split()
def __iter__(self):
return SentenceIter(self.words)
class SentenceIter:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
现在sen3可以一直迭代了
>>> sen3 = Sentence3('hello my world my name is Alfred')
>>> sen3.words
['hello', 'my', 'world', 'my', 'name', 'is', 'Alfred']
>>> for i in sen3:
print(i)
hello
my
world
my
name
is
Alfred
>>> for i in sen3:
print(i)
hello
my
world
my
name
is
Alfred
>>>
总的来说:
- 可迭代对象应该是实现了__getitem__的对象
- 迭代器应该是实现了__iter__的对象,__iter__方法返回一个迭代器,而不是使其成为一个迭代器,__next__方法应该封装在迭代器的内部。