Python迭代器与可迭代对象

当扫描内存中放不下的数据集时,我们需要找到一种惰性获取数据项的方式,每次“取出”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__方法应该封装在迭代器的内部。

猜你喜欢

转载自blog.csdn.net/eighttoes/article/details/85839852