为什么在Python中,在类上定义__getitem__方法会使其可迭代?

86
为什么在类上定义 __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个回答

82

迭代器对__getitem__的支持可以被视为一个“遗留特性”,它允许在PEP234引入可迭代性作为主要概念时更加平滑地过渡。它仅适用于没有__iter__的类,其__getitem__接受整数0、1等,并在索引过高(如果有)时引发IndexError,通常是在__iter__出现之前编写的“序列”类(尽管你也可以用这种方式编写新类)。

个人而言,在新代码中我宁愿不依赖于此,虽然它并未被弃用,也不会消失(在Python 3中也可以正常工作),因此这只是一种风格和品味的问题(“显式优于隐式”,所以我宁愿明确地支持可迭代性,而不是依赖于__getitem__隐式地支持它——但这不是什么大问题)。


1
当引入__getitem__来创建可迭代对象时,是否考虑了使用[]属性访问的情况?从阅读答案来看,如果您想要[]属性访问和可迭代性,则不能仅使用__getitem__。因此,您应该使用__getitem__进行[]访问,并使用__iter__进行迭代器,对吗? - malana
另一个反对 __getitem__ 的观点是,任何整数类型都会有一个上限,因此索引访问可能会遇到这个问题。而 __next__ 可以一直愉快地进行下去。 (至少,那是我的理解。) - Jon Coombs
2
只是注意到Python的int类型没有限制,它是一种任意大小的整数。 - MaxNoe

58

如果您查看定义迭代器的PEP234,它会说:

  1. 如果一个对象实现了__iter__()__getitem__(),则可以使用for进行迭代。

  2. 如果一个对象实现了next(),则可以将其作为迭代器使用。


44

__getitem__比迭代器协议更早出现,曾经是使对象可迭代的唯一方法。因此,它仍然被支持作为迭代的一种方式。迭代的协议如下:

  1. 检查是否存在__iter__方法。如果存在,使用新的迭代协议。

  2. 否则,尝试使用连续增加的整数值调用 __getitem__,直到引发 IndexError 为止。

(2) 过去是实现迭代的惟一方法,但缺点是它假设了超出支持迭代所需的部分。要支持迭代,您必须支持随机访问,这对于文件或网络流等事物而言非常昂贵,因为前进容易,而后退需要存储所有内容。 __iter__ 允许无需随机访问即可进行迭代,但由于随机访问通常也允许迭代,并且破坏向后兼容性将是不好的,因此仍支持使用 __getitem__


是的,谢谢。这回答了我之前的“如果它没坏,为什么要修理它?”的问题。 - m1keil

8

特殊方法例如__getitem__可以为对象添加特殊行为,包括迭代。

http://docs.python.org/reference/datamodel.html#object.getitem

"for循环期望在非法索引处引发IndexError以允许正确检测序列的结尾。"

引发IndexError以表示序列结尾。

你的代码基本等同于:

i = 0
while True:
    try:
        yield object[i]
        i += 1
    except IndexError:
        break

在for循环中,object是您要迭代的内容。


5

这是由于历史原因造成的。在 Python 2.2 之前,__getitem__ 是创建可用 for 循环遍历的类的唯一方法。在 2.2 中,__iter__ 协议被添加,但为了保持向后兼容性,__getitem__ 在 for 循环中仍然起作用。


1

因为cb[0]cb.__getitem__(0)是相同的。请参阅Python文档


3
正确,但 getitem() 不同于 iter()。 - grieve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接