与 NumPy 实例调用 `__bool__` 的相等比较

Equality Comparison with NumPy Instance Invokes `__bool__`

我定义了一个class,其中它的__ge__方法returns是它自己的一个实例,并且不允许调用它的__bool__方法(类似于Pandas Series).

为什么 X.__bool__np.int8(0) <= x 期间被调用,而其他任何示例都没有? 谁在调用它?我已经阅读了 Data Model 文档,但我没有在那里找到我的答案。

import numpy as np
import pandas as pd

class X:
    def __bool__(self):
        print(f"{self}.__bool__")
        assert False
    def __ge__(self, other):
        print(f"{self}.__ge__")
        return X()

x = X()

np.int8(0) <= x

# Console output:
# <__main__.X object at 0x000001BAC70D5C70>.__ge__
# <__main__.X object at 0x000001BAC70D5D90>.__bool__
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "<stdin>", line 4, in __bool__
# AssertionError

0 <= x

# Console output:
# <__main__.X object at 0x000001BAC70D5C70>.__ge__
# <__main__.X object at 0x000001BAC70D5DF0>

x >= np.int8(0)

# Console output:
# <__main__.X object at 0x000001BAC70D5C70>.__ge__
# <__main__.X object at 0x000001BAC70D5D30>


pd_ge = pd.Series.__ge__
def ge_wrapper(self, other):
    print("pd.Series.__ge__")
    return pd_ge(self, other)

pd.Series.__ge__ = ge_wrapper

pd_bool = pd.Series.__bool__
def bool_wrapper(self):
    print("pd.Series.__bool__")
    return pd_bool(self)

pd.Series.__bool__ = bool_wrapper


np.int8(0) <= pd.Series([1,2,3])

# Console output:
# pd.Series.__ge__
# 0    True
# 1    True
# 2    True
# dtype: bool

我怀疑 np.int8.__le__ 的定义不是 returning NotImplemented 并让 X.__ge__ 接管,而是尝试 return 一些东西像 not (np.int(8) > x),然后 然后 np.int8.__gt__ 加注 NotImplemented。一旦 X.__gt__(x, np.int8(0)) return 是 X 的实例而不是布尔值,那么我们需要调用 x.__bool__() 来计算 not x 的值。

(仍在尝试追踪定义 int8.__gt__ 的位置以确认。)

(更新:不完全是。int8 使用单个通用的丰富比较函数,该函数简单地将值转换为 0 维数组,然后 returns PyObject_RichCompare 的结果在数组和 x.)


我确实发现 this function 似乎最终实现了 np.int8.__le__:

static NPY_INLINE int
rational_le(rational x, rational y) {
    return !rational_lt(y,x);
}

我不清楚如果其中一个参数(如 X)不是 NumPy 类型,我们如何避免使用此函数。我想我放弃了。

TL;DR

X.__array_priority__ = 1000


最大的提示是它适用于 pd.Series

首先,我尝试让 X 继承自 pd.Series。这有效(即 __bool__ 不再调用)。

为了确定 NumPy 是否使用 isinstance 检查或鸭子类型方法,我删除了显式继承并添加(基于 this answer):

@property
def __class__(self):
    return pd.Series

操作不再有效(即调用了 __bool__)。

所以现在我认为我们可以得出结论,NumPy 正在使用鸭子类型的方法。所以我查看了 X.

上访问了哪些属性

我在 X 中添加了以下内容:

def __getattribute__(self, item):
    print("getattr", item)
    return object.__getattribute__(self, item)

再次将 X 实例化为 x,并调用 np.int8(0) <= x,我们得到:

getattr __array_priority__
getattr __array_priority__
getattr __array_priority__
getattr __array_struct__
getattr __array_interface__
getattr __array__
getattr __array_prepare__
<__main__.X object at 0x000002022AB5DBE0>.__ge__
<__main__.X object at 0x000002021A73BE50>.__bool__
getattr __array_struct__
getattr __array_interface__
getattr __array__
Traceback (most recent call last):
  File "<stdin>", line 32, in <module>
    np.int8(0) <= x
  File "<stdin>", line 21, in __bool__
    assert False
AssertionError

啊哈!什么是 __array_priority__?谁在乎,真的。稍加挖掘,我们只需要知道 NDFramepd.Series 继承)将此值设置为 1000.

如果我们添加 X.__array_priority__ = 1000,就可以了! __bool__ 不再被调用。

之所以如此困难(我相信)是因为 NumPy 代码没有出现在调用堆栈中,因为它是用 C 编写的。如果我尝试了建议,我可以进一步调查 .