在使用Python时,我们经常会使用for循环来访问容器对象(列表、字符、字典等)中的元素。其幕后实际是通过迭代协议来完成的,迭代是一种依次访问对象中元素的方式,for循环在对象上调用iter()函数生成一个迭代器(对象要是可迭代的,即支持迭代协议),然后通过迭代器对元素进行访问。
文章目录
一、for循环的实现
考虑下面一个简单for循环
numbers = [1,2,3]
for num in numbers:
print(num)
当for循环作用在numbers变量上时,它实际依次做了下面的动作:
- 调用iter(numbers),此函数会返回一个迭代器对象,该对象会包含一个__next()__方法,调用此方法会逐一访问对象中的元素。
- 重复调用对象的__next()__方法,逐个返回对象中的元素。
- 元素用尽时,迭代器会抛出StopIteration异常,for循环捕获到该异常,退出for循环,执行结束。
因此for循环幕后的执行过程就是:
it = iter(numbers) # 返回迭代器对象,赋给变量it
it.__next__() # 调用迭代器对象的__next__()方法,返回1。也可以使用next(it)来调用对象上的__next__()方法
it.__next__() # 返回2
it.__next__() # 返回3
it.__next__() # 元素耗尽,抛出StopIteration异常,for循环退出
二、自定义迭代行为
知道了迭代的原理,我们就可以通过自定义__next__()方法,改造for循环的迭代行为。
例如下面自定义一个迭代类,从第一个元素开始,每次迭代跳过一个元素。为了满足这个需求只需要自定义__next__()方法,每次给索引步长加2即可,:
class SkipOneElement:
def __init__(self, data):
self.data = data
self.index = -2
def __iter__(self):
return self
def __next__(self): # 自定义__next__()方法,for循环会自动调用此方法
self.index += 2 # self.index = -2加上步长,初始的索引就是0,随后每次增加2,即跳过一个元素
if self.index >= len(self.data) or len(self.data) == 0: # 当索引超过对象长度或对象为空,抛出StopIteration异常
raise StopIteration
return self.data[self.index] # 返回索引指向的元素
下面用range(10)来测试效果,常规的循环如下:
for num in range(10):
print(num)
自定义迭代效果如下,可以看到从索引为0的元素开始,后续每次迭代都跳过了一个元素:
for num in SkipOneElement(range(10)):
print(num)
如果想要反向迭代,那么初始索引只需要设置为len(self.data),然后每次减1即可。总之,知道了for循环的运作方式,你就可以根据需求随意调整__next__()方法,达到自定义迭代行为的目的。