为什么`x[i]`不等同于`x.__getitem__(i)`?

4

根据官方文档

x[i] 大致等同于 type(x).__getitem__(x, i)

相较于看似更简单的 x.__getitem__(i),上述方式有什么好处呢?
编辑:为什么Python会这样实现?

下面展示一段代码,说明标准行为的一个缺点:最后一个断言失败了,而调用__getitem__的倒数第二个却通过了。

def poww_bar(base):
    class Bar():
        def __getitem__(self, x):
            return lambda: base**x
    return Bar()

def poww_foo(base):
    class Foo():
        pass
    f = Foo()
    f.__getitem__ = lambda x: lambda: base ** x
    return f

pow_bar2 = poww_bar(2)
pow_foo2 = poww_foo(2)


assert pow_bar2.__getitem__(3)() == 8 # OK
assert pow_bar2[3]() == 8 # OK

assert pow_foo2.__getitem__(3)() == 8 # OK
assert pow_foo2[3]() == 8 # TypeError: 'Foo' object is not subscriptable

它失败了,可能是因为仅仅使用 f.__getitem__ = ... 是不足以定义索引运算符的。也许当你使用 def __getitem__(...): ... 时,解释器会在幕后执行一些魔法,将此函数注册为特殊函数,并有资格使用索引语法糖。 - Alexey Larionov
2
此外,您的 f.__getitem__ 版本仅适用于 Foo 类的单个实例 f,而不是整个类(它只接受1个参数,与标准版本中的 (self, x) 相反)。 - Alexey Larionov
1
@AlexeyLarionov 看起来 OP 已经意识到了这一点,并且正在询问 Python 为什么会这样行事。 - Konrad Rudolph
2个回答

1

方法是类属性,而不是实例属性。

pow_bar2 没有名为 __getitem__ 的实例属性。所以查找过程继续检查该名称的类属性,并成功找到 Bar.__getitem__

但过程并没有结束。pow_bar2.__getitem__(i) 不等同于 Bar.__getitem__(i),因为 Python 首先检查属性查找是否产生实现 描述符协议 的对象。由于 Bar.__getitem__function 的一个实例,它确实实现了描述符协议。

接下来的步骤是返回不是函数本身,而是Bar.__dict__['__getitem__'].__get__(pow_bar2, Bar)的结果。(我切换到使用Bar.__dict__以强调我们不会进入无限循环触发描述符协议。) 这是method的一个实例,它本身是一个可调用对象,将自己的参数与pow_bar2一起作为原始函数的参数传递。

因此,pow_bar2.__getitem__(i)等同于Bar.__dict__['__getitem__'].__get__(pow_bar2, Bar)(i),这大致相当于Bar.__dict__['__getitem__'](pow_bar2, i)

但实际上,pow_bar2[i] 只是更短,更容易识别(由于其他语言对此语法的支持已有几十年的历史),比 pow_bar2.__getitem__(i) 更容易识别。 __getitem__ 是使使用 [] 可扩展 到其他类,而不仅限于内置类型的方法。


描述符协议不仅仅是一个一次性的功能,使实例方法的行为看起来比必要的更加复杂。它还确定类方法、静态方法和属性的工作方式,并且可以进一步用于自定义其他属性行为。

0

这可能只是一种优化。类函数在类定义中只有一个引用。对象函数将在每个对象中都有一个引用。因此,__getitem__方法被指定为类函数,因此他们不需要浪费时间在对象定义中查找它。

当然,这都是猜测。


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