python – 什么时候`__index__`被调用列表和numpy数组?

从几天前开始,我一直没有意识到__index __()方法,直到读到
this question.之后,我在
documentation,
PEP
other SO questions中一直在阅读它.

我明白,只要[]运算符用于可以切片的对象(在我的情况下我对列表,numpy数组和pandas感兴趣),就可以获得切片或索引的值,以便lst [key] = lst [key .__ index __()]已经完成.

但是,正如在其中一个问题中,结果取决于是否使用了PyPy或CPython,因此我决定检查何时使用__index__实际完成切片以及何时不切实.我做了以下(在CPython 2.7.14中):

lst = range(10)
array = np.arange(10)
series = pd.Series(lst)

并定义了以下类:

class MyIndex:
    def __index__(self):
        return 2
class MyInt(int):
    def __index__(self):
        return 3
class MyStr(str):
    def __index__(self):
        return 4

然后我尝试使用此已使用的已定义对象访问已定义的对象,获取以下内容:

注意:出于可读性目的,我没有发布完整的错误消息.

对于MyIndex类,预期输出2:

print lst[MyIndex()]
print array[MyIndex()]
print series[MyIndex()]
# Output:
2
2
AttributeError: MyIndex instance has no attribute '__trunc__'

对于MyInt类,预期输出3:

# Case 1
print lst[MyInt()]
print array[MyInt()]
print series[MyInt()]
# Output
0
0
0

# Case 2
print lst[MyInt(2)]
print array[MyInt(2)]
print series[MyInt(2)]
# Output
2
2
2

对于MyStr类,预期输出4:

# Case 1
print lst[MyStr()]
print array[MyStr()]
print series[MyStr()]
# Output
4
4
KeyError: ''

# Case 2
print lst[MyStr('a')]
print array[MyStr('a')]
print series[MyStr('a')]
# Output
4
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
KeyError: 'a'

我真的对此感到困惑,主要有以下几点:

>使用列表时,使用__index__方法,但不使用int及其子项.
> Numpy使用__index__之类的列表,但在最后一种情况下,MyStr(‘a’)会引发错误.我错过了什么或者在这种情况下__index__仅在MyStr为空字符串时使用?
> Pandas切片是一个整个世界,甚至接受有序字符串索引的切片,因此可以解除不使用__index__.因此,我唯一关于pandas的问题是,如果代码的输出可能会有所不同,具体取决于python实现.

我的问题基本上是标题中的一个:

When is __index__ called for lists and numpy arrays? Why are there some exceptions?

话虽如此,我很乐意收到我可能错过的有关此方法的任何额外信息.

最佳答案 首先,为__index__引用
docs

Called to implement operator.index(), and whenever Python needs to
losslessly convert the numeric object to an integer object (such as in
slicing, or in the built-in bin(), hex() and oct() functions).
Presence of this method indicates that the numeric object is an
integer type. Must return an integer.

Note: In order to have a coherent integer type class, when __index__()
is defined __int__() should also be defined, and both should return
the same value.

如果对象已经是int,则通常不会调用__index__,​​因为不需要转换.此外,您需要一个__int__方法来使用__index__;你的一些问题来自于此. (你的MyInt继承了int .__ int __,但是它的__index__行为与它从int继承的行为不一致,所以这也是一个问题.)

在CPython中,list实现了C级序列协议,CPython在调用序列协议之前自动调用__index__作为非int. Int只是使用它们的int值,而你的MyInt()的int值为0.如果需要,可以跟踪__index__到PyObject_GetItem,PyNumber_AsSsize_tPyNumber_Index的调用链.

NumPy数组不使用序列协议进行索引.他们实现了它,但他们也实现了优先级映射协议. NumPy数组自己处理索引处理.

他们尝试的一件事是PyNumber_Index,这就是为什么它们的行为类似于大多数测试的列表.但是,NumPy数组支持比列表更复杂的索引,NumPy数组索引实现的一部分是weird special case,其中某些非元组序列被视为索引元组.

您的MyStr对象是序列,MyStr(‘a’)触发特殊情况.它被视为元组(MyStr(‘a’))或(‘a’,),它不是有效的索引元组.

至于Pandas,pandas.Series在Python级别实现__getitem__.它还必须手动处理索引.

对于MyIndex(),看起来它试图在MyIndex()对象上调用int,因为你没有__int__方法而失败了.错误通常是一个TypeError,Pandas可能会以不同的方式处理,但是你忘了继承对象,所以你得到了一个经典的类,这些都很奇怪.

您的MyInt()对象是整数并用作整数,与列表和数组测试相同.

你的MyStr()对象是字符串,Pandas将它们视为字符串,而不是试图将它们解释为int.

点赞