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

时间:2018-04-05 17:58:08

标签: python numpy indexing slice

几天前我在阅读this question之前不知道__index__()方法。之后,我一直在documentationPEPother 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实现而有所不同。

我的问题基本上就是标题中的问题:

  

__index__何时调用列表和numpy数组?为什么会有例外?

话虽如此,我很乐意收到有关这种方法的任何额外信息。

1 个答案:

答案 0 :(得分:1)

首先,引用__index__的{​​{3}}:

  

调用实现operator.index(),并且只要Python需要   无损地将数字对象转换为整数对象(例如   切片,或者在内置的bin(),hex()和oct()函数中)。   此方法的存在表明数字对象是   整数类型。必须返回一个整数。

     

注意:为了拥有一个连贯的整数类型,__index__()   定义__int__()也应定义,两者都应该返回   相同的价值。

如果对象已经是int,则通常不会调用

__index__,因为不需要转换。此外,您还需要使用__int__方法__index__;你的一些问题来自于此。 (您的MyInt会继承int.__int__,但其__index__行为与从int继承的行为不一致,因此这也是一个问题。)

在CPython中,list实现了C级序列协议,CPython在调用序列协议之前自动调用__index__非int。 Ints只是使用了他们的int值,而你的MyInt()的int值为0.你可以通过docsPyObject_GetItem和{{跟踪__index__的调用链3}}如果你想要。

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

他们尝试的一件事是PyNumber_Index,这就是为什么他们的行为就像大多数测试的列表一样。但是,NumPy数组支持比列表更复杂的索引,NumPy数组索引实现的一部分是PyNumber_AsSsize_t,其中某些非元组序列被视为索引元组。

您的MyStr个对象是序列,MyStr('a')触发特殊情况。它被视为tuple(MyStr('a'))('a',),这不是有效的索引元组。

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

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

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

您的MyStr()个对象是字符串,Pandas将它们视为字符串,而不是试图将它们解释为整数。