为什么在类中定义__getitem__使其在Python中可迭代?

87 投票
6 回答
36897 浏览
提问于 2025-04-15 11:55

为什么在一个类里面定义了 __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 个回答

44

__getitem__ 是在迭代器协议出现之前就存在的,过去它是让对象可以被迭代的唯一方法。因此,它现在仍然被支持作为一种迭代方式。简单来说,迭代的规则是:

  1. 先检查是否有 __iter__ 方法。如果有,就用新的迭代协议。

  2. 如果没有,就尝试用逐渐增大的整数值去调用 __getitem__,直到出现 IndexError 错误为止。

以前(2)是唯一的实现方式,但它的缺点是需要支持比单纯迭代更多的功能。为了支持迭代,你必须支持随机访问,这对像文件或网络流这样的东西来说成本很高,因为向前读取很简单,但向后读取就需要存储所有内容。__iter__ 允许在不支持随机访问的情况下进行迭代,但因为随机访问通常也能进行迭代,而且打破向后兼容性是不好的,所以 __getitem__ 仍然被支持。

83

支持迭代的 __getitem__ 可以看作是一种“遗留特性”,它让在 PEP234 引入可迭代性这个概念时,过渡变得更加顺畅。这个特性只适用于那些没有 __iter__ 方法的类,这些类的 __getitem__ 方法接受整数 0、1 等等,当索引过高时会抛出 IndexError 错误(如果有的话),通常是一些在 __iter__ 出现之前编写的“序列”类(不过你也可以用这种方式编写新的类)。

就我个人而言,我不太想在新的代码中依赖这个特性,虽然它并没有被弃用,也不会消失(在 Python 3 中也能正常工作),所以这只是风格和个人喜好的问题(“明确比隐含更好”,所以我更愿意明确地支持可迭代性,而不是依赖 __getitem__ 隐含地为我支持它——不过这也不是个大问题)。

58

如果你看看定义迭代器的 PEP234,里面提到:

  1. 一个对象可以用 for 循环来遍历,如果它实现了 __iter__()__getitem__() 这两个方法。

  2. 一个对象可以作为迭代器使用,如果它实现了 next() 这个方法。

撰写回答