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

Posted

技术标签:

【中文标题】什么时候为列表和 numpy 数组调用 __index__?【英文标题】:When is `__index__` called for lists and numpy arrays? 【发布时间】:2018-09-15 15:32:54 【问题描述】:

从几天前开始,直到阅读this question,我才知道__index__() 方法。在那之后,我一直在documentation、PEP 和other SO questions 中阅读它。

我了解到,每当在可以切片的对象中使用 [] 运算符时(在我的情况下,我对列表、numpy 数组和 pandas 感兴趣),都会获得切片或索引的值,以便 @ 987654327@ 已完成。

但是,与其中一个问题一样,结果取决于使用的是 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 数组?为什么会有一些例外?

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

【问题讨论】:

__getitem__ 方法定义一个类,看看索引给它的args 元组是很有启发性的。 numpy index_tricks.py 使用它来创建一些伪索引函数,例如np.r_np.mgrid 只有在最近的版本中,numpy 才拒绝将浮点数作为索引。它曾经允许它们,根据需要截断。 【参考方案1】:

首先,将docs 引用为__index__

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

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

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


在 CPython 中,列表实现了 C 级别的序列协议,并且 CPython 在调用序列协议之前会自动为非整数调用 __index__。 Int 只是使用它们的 int 值,而您的 MyInt() 的 int 值为 0。如果需要,您可以通过 PyObject_GetItemPyNumber_AsSsize_tPyNumber_Index 跟踪 __index__ 的调用链。


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

他们尝试的其中一个方法是PyNumber_Index,这就是为什么它们在大多数测试中表现得像列表。但是,NumPy 数组支持比列表更复杂的索引,并且 NumPy 数组索引实现的一部分是weird special case,其中某些非元组序列被视为索引元组。

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


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

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

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

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

【讨论】:

现在我唯一的疑问是对于 index 返回的值与 int 不同的 int 应该如何处理?在 PyPy 中,它看起来像 index 优先(***.com/questions/49633222/…) @xg.plt.py:如果您的__int__ 和您的__index__ 不匹配,则您的对象已损坏。 Python 没有指定哪一个获胜,您从检查实现中收集到的任何内容都只是实现细节,如有更改,恕不另行通知。

以上是关于什么时候为列表和 numpy 数组调用 __index__?的主要内容,如果未能解决你的问题,请参考以下文章

展平和取消展平 numpy 数组的嵌套列表

使用列表中的标签将 numpy 数组转换为 pandas 数据框

如何使用 pybind 传递 numpy 数组列表

Cython 优化 numpy 数组求和的关键部分

使用Numpy而不在数组中获得额外的尺寸

numpy 使用