说到Python编程语言,最令人印象深刻的应该就是它的易用性了。为了提供易用性,语言中封装了大量的常用数据结构、算法和类库,并创建了不少
与其他语言不同的概念。其中,大部分概念都非常容易理解。然而,仍有些概念比较相似,常常使初学者混淆,比如迭代器和可迭代对象。
有编程经验的开发者都知道,迭代(或称循环)是处理大量数据时非常常用的手段。
从普通对象到迭代器
查看下面一个常规的类定义:
class SimpleClass1:
pass
simple1 = SimpleClass1()
如果从simple对象获取数据:
next(simple1)
将会报错“TypeError: 'SimpleClass1' object is not an iterator”,这是因为simple1对象不是一个迭代器。
下面介绍Python中的可迭代协议。
如果要使一个对象成为一个迭代器,需要:
实现无参数的“__next__”方法,返回下一个数据;
当没有下一个数据时,抛出一个特殊的异常StopIteration。
那么,重新实现SimpleClass,如下:
class SimpleClass2:
def __init__(self, name):
self.name = name
self.current = 0
def __next__(self):
if self.current >= len(self.name):
raise StopIteration
nextval = self.name[self.current]
self.current += 1
return nextval
simple2 = SimpleClass2('abc')
重新使用next函数就可以获取数据了:
next(simple2) # 返回a
next(simple2) # 返回b
next(simple2) # 返回c
next(simple2) # 抛出异常 StopIteration
如上所示,迭代器可以成功返回数据,如预期那样。但是每次都使用next函数获取数据还是比较麻烦,更不用说还要去处理异常。
从迭代器到可迭代对象
如果在开发中,对象能够直接支持for循环来进行遍历,并且自动处理StopIteration异常,那么实际开发工作将会简单许多。
于是Python中引入了可迭代对象的概念,可迭代对象就是能够支持使用iter来获取迭代器的对象。我们可以在类中实现__iter__方法来支持iter函数:
class SimpleClass3:
def __init__(self, name):
self.name = name
self.current = 0
def __next__(self):
if self.current >= len(self.name):
raise StopIteration
nextval = self.name[self.current]
self.current += 1
return nextval
def __iter__(self):
print('__iter__方法被调用')
return self
simple3 = SimpleClass3('abc')
使用for循环打印元素:
for item in simple3:
print(item)
将会顺序输出 a, b, c三个元素,for循环语句会自动调用iter获取此可迭代对象的迭代器,并自动处理异常。
Python可迭代协议使用实例
以上就是Python中的可迭代协议。下面使用该协议仿照系统内置range实现一个简化版本的类SimpleRange,它支持返回从0到n(不包括)的整数值。
class _SimpleRange:
def __init__(self, n):
self.n = n
self.current = 0
def __iter__(self):
return self
def __next__(self):
"""支持获取下一个元素"""
if self.current >= self.n:
raise StopIteration # 当没有下一个元素时抛出异常
next_val = self.current # 保存当前值以便返回
self.current += 1
return next_val
class SimpleRange:
"""简化版本的range"""
def __init__(self, n):
"""初始化对象"""
self.n = n
def __iter__(self):
"""支持返回迭代器"""
return _SimpleRange(self.n)
simple_range = SimpleRange(10)
r = range(10)
assert list(simple_range) == list(r)
assert list(simple_range) == list(r) # 该断言会成功通过
上面的代码中,_SimpleRange实现了__next__方法,所以其对象是一个迭代器。而SimpleRange实现了_iter__方法,并且在其中返回一个新的_SimpleRange对象。SimpleRange是一个可迭代对象。
需要注意的是,在SimpleRange对象中每次调用iter都会返回一个全新的迭代器(即_SimpleRange对象),这就是上面代码中,第二个断言能够通过的原因。
下面看第二个例子,定义一个列表如下:
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9]
我们知道,lst是可迭代对象,所以可以使用iter函数获取其迭代器iter(lst)。而如果将同一个迭代器放入zip函数,可以同时分别从
同一个迭代器获取数据,即:
lst_iter = iter(lst)
assert list(zip(lst_iter, lst_iter, lst_iter)) == [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
将上面的代码组合在一起,配合拆包则可以使用代码:
list(zip(*[iter(lst)]*3))
将列表 [1, 2, 3, 4, 5, 6, 7, 8, 9],转换为 [(1, 2, 3), (4, 5, 6), (7, 8, 9)]。
特殊的可迭代对象
除了标准的实现可迭代的方法(即实现__iter__方法)外,如果一个类实现了__getitem__方法,并且其索引是从0开始的整数,则
其对象也是可迭代对象。如:
class SimpleClass4:
def __init__(self, n):
self.n = n
def __getitem__(self, idx):
if idx < self.n:
return idx
raise StopIteration
总结
可迭代对象就是可以用来拿到迭代器的对象,而迭代器可以用来获取下一个数据。
可迭代对象实现了返回迭代器的__iter__方法或者使用从0开始的整数索引的__getitem__方法;迭代器实现了获取下一个元素的__next__方法,当没有下一个元素时,迭代器会抛出一个特殊的异常StopIteration。
Python中的许多结构内置支持可迭代协议,会自动处理StopIteration异常,如for循环、拆包等。