为什么在类中定义__getitem__使其在Python中可迭代?
为什么在一个类里面定义了 __getitem__
方法就能让这个类变得可迭代呢?
比如说,如果我写了:
class B:
def __getitem__(self, k):
return k
cb = B()
for k in cb:
print k
我得到的结果是:
0
1
2
3
4
5
...
我本来以为在 for k in cb:
这里会出现一个错误呢。
6 个回答
__getitem__
是在迭代器协议出现之前就存在的,过去它是让对象可以被迭代的唯一方法。因此,它现在仍然被支持作为一种迭代方式。简单来说,迭代的规则是:
先检查是否有
__iter__
方法。如果有,就用新的迭代协议。如果没有,就尝试用逐渐增大的整数值去调用
__getitem__
,直到出现 IndexError 错误为止。
以前(2)是唯一的实现方式,但它的缺点是需要支持比单纯迭代更多的功能。为了支持迭代,你必须支持随机访问,这对像文件或网络流这样的东西来说成本很高,因为向前读取很简单,但向后读取就需要存储所有内容。__iter__
允许在不支持随机访问的情况下进行迭代,但因为随机访问通常也能进行迭代,而且打破向后兼容性是不好的,所以 __getitem__
仍然被支持。
支持迭代的 __getitem__
可以看作是一种“遗留特性”,它让在 PEP234 引入可迭代性这个概念时,过渡变得更加顺畅。这个特性只适用于那些没有 __iter__
方法的类,这些类的 __getitem__
方法接受整数 0、1 等等,当索引过高时会抛出 IndexError
错误(如果有的话),通常是一些在 __iter__
出现之前编写的“序列”类(不过你也可以用这种方式编写新的类)。
就我个人而言,我不太想在新的代码中依赖这个特性,虽然它并没有被弃用,也不会消失(在 Python 3 中也能正常工作),所以这只是风格和个人喜好的问题(“明确比隐含更好”,所以我更愿意明确地支持可迭代性,而不是依赖 __getitem__
隐含地为我支持它——不过这也不是个大问题)。
如果你看看定义迭代器的 PEP234,里面提到:
一个对象可以用
for
循环来遍历,如果它实现了__iter__()
或__getitem__()
这两个方法。一个对象可以作为迭代器使用,如果它实现了
next()
这个方法。