前言
Python的特色之一是基于协议实现功能。比如改变一个加号(+
)的行为,在C++中需要操作符重载,在Python中则是重写__add__
方法。为了描述可迭代对象和迭代器,Python提供了两个魔法方法,分别是__iter__
和__next__
。又为了支持for...in...
行为,牵扯进了__getitem__
。
我们先从可迭代对象说起。
Iterable
Python在collections.abc模板中提供了Iterable,用于判断对象是否可迭代,还提供了Iterator,判断一个对象是否是迭代器。这里拿熟知的列表类型试水:
lyst = ["t", "y"]
print(isinstance(lyst, Iterable)) # 输出:True
print(isinstance(lyst, Iterator)) # 输出:False
可见list只是一个可迭代对象,而非迭代器。
让一个对象成为可迭代对象只需要添加__iter__
方法。
class Students(object):
def __iter__(self):
pass
if __name__ == "__main__":
stu = Students()
print(isinstance(stu, Iterable)) # 输出:True
尽管上面看来其有些欺诈——如果真的将它迭代会发现解释器报错TypeError。但不可否认的是,此时解释器认为stu是一个可迭代对象了(涉及到isinstance()的处理机制,可见《Python中的abc模块》),所以isinstance()
返回出True。
Iterator
与可迭代对象最大的区别在于,可以对迭代器使用next()
方法。认为,迭代器是特殊的可迭代对象,它需要__iter__
和__next__
两个协议支持。
用之前的欺骗手法做个测试:
class Students(object):
def __iter__(self):
pass
def __next__(self):
pass
if __name__ == "__main__":
stu = Students()
print(isinstance(stu, Iterable)) # 输出:True
print(isinstance(stu, Iterator)) # 输出:True
对象stu是可迭代对象,也是迭代器。打印结果证实了前边说法。
for...in...
直接在协议函数下面写pass
,让其充装可迭代对象、迭代器,这是利用了Python鸭子类型的特点。然而,如果希望对象可以正常的被for...in...
遍历,有些逻辑需要我们手动实现。
先说__iter__
方法,它只需要完成一个任务——返回一个迭代器。如果我们不返回迭代器,将引发报错:
class Students(object):
def __iter__(self):
return [0, 1, 2] # 列表类型是可迭代对象,而不是迭代器
if __name__ == "__main__":
stu = Students()
for s in stu:
print(s)
# 输出:
Traceback (most recent call last):
File "xxx", line 50, in <module>
for s in stu:
TypeError: iter() returned non-iterator of type 'list'
现在试着返回一个生成器(生成器是特殊的迭代器):
class Students(object):
def __iter__(self):
return (i for i in range(3))
if __name__ == "__main__":
stu = Students()
for s in stu:
print(s)
# 输出:
0
1
2
事实上,在for...in...
过程中,Python会自动调用iter()
函数,也就是说for i in x
被转换成了for i in iter(x)
处理。想要继续运行下去,得先保证iter()
函数不会抛错。
基于前面结论,__iter__
不负责逻辑处理,那么处理逻辑就得写在__next__
方法里面。像下面这样:
class Students(object):
def __init__(self):
self.items = [1, 2, 3, 4]
self.index = 0
def __next__(self):
if self.index >= len(self.items):
raise StopIteration
result = self.items[self.index]
self.index += 1
return result
此时还不可以对Students的实例对象使用for遍历,但可以通过next()
访问内部元素:
print(next(stu))
print(next(stu))
print(next(stu))
# 输出:
1
2
3
之前也说过了,迭代器需要实现__iter__
和__next__
两个方法,所以我们得添加__iter__
。又因为__iter__
的任务是返回一个迭代器,拥有了__iter__
和__next__
方法的Students的对象本身就是一个迭代器(似乎有点绕),所以返回自身(self)即可:
class Students(object):
# ...
def __iter__(self):
return self
# ...
另外需要说明的是,在for...in...
遍历过程中,需要一个终止信号,这个信号就是StopIteration。for..in..遍历的时候等同于:
while True:
try:
print(next(stu))
except StopIteration:
break
getitem
__getitem__
的作用可见《Python中的切片》,for...in...
运作的完整流程需要用到这个方法。从问第一个问题开始:
问题1:对象中有
__iter__
方法吗?- 如果有,调用
iter(对象)
;(此时StopIteration是终止信号,for..in..会自动处理StopIteration) - 如果没有,进入问题2。
- 如果有,调用
问题2:对象中有
__getitem__
方法吗?- 如果有,从0开始传入索引值访问内部元素,之后索引值依次累加:1,2,3,...(此时IndexError是终止信号,for..in..会自动处理IndexError)
- 如果没有,抛出异常TypeError。
感谢
- 参考慕课Bobby老师课程Python高级编程和异步IO并发编程
还不快抢沙发