自定义类中左加法/右加法与 numpy 数组之间的不同行为

问题描述 投票:0回答:1

我正在编写一个类,其中存储的属性之一在构造函数中被转换为整数。我还重载了左/右加法,其中添加/减去一个整数意味着将此属性移动该整数。原则上,加法在我的班级的所有上下文中都可以通勤,因此作为用户,我不希望左右加法之间有任何差异。

但是,我的代码不同意。我天真地期望 numpy 数组的加法会失败。该代码对于左加法的行为与预期一致,但对于右加法则错误地运行!这是我的意思的一个极其简单的工作示例:

import numpy as np

class foo:

    def __init__(self, val):
        self.val = int(val)

    def __add__(self, other):
        if isinstance(other, self.__class__):
            return foo(self.val + other.val)
        try:
            return foo(self.val + int(other))
        except:
            raise ValueError(f"unsupported operand type(s) for +: {type(self).__name__} and {type(other).__name__}")

    __radd__ = __add__  

    def __repr__(self):
        return str(self.val)   

然后,当我运行以下块时:

a = foo(6)
b = np.arange(10)
>>> print(f"left addition by 'a': {a + b}")

我得到了预期的异常。但是当我运行这个代码块时,它运行得很好。

>>> print(f"right addition by 'a': {b + a}")
right addition by 'a': [6 7 8 9 10 11 12 13 14 15] # Adds by b element-wise

似乎右加法默认为 numpy 数组的加法重载方法,正如 radd 文档中所预期的那样(强调我的)

调用这些方法来实现二进制算术运算 (

+
,
-
,
*
,
@
,
/
,
//
,
%
,
divmod()
,
pow()
**
<<
>>
&
^
|
)以及反射(交换)操作数。 仅当左操作数不支持相应运算[3]且操作数类型不同时,才会调用这些函数。 [4] 例如,计算表达式
x - y
,其中y是具有
__rsub__()
方法的类的实例,如果
type(y).__rsub__(y, x)
返回
type(x).__sub__(x, y)
,则调用 
NotImplemented

所以我认为 numpy 聪明地意识到我自己的类使用 numpy 数组内的 dtype 实现加法并对其进行循环。

就其价值而言,我一点也不喜欢这个功能。我只是不喜欢我的加法操作在左右加法操作之间的行为不同。我正在寻找两种情况下的错误(可能为了简单起见更可取)或在两种情况下工作。我不确定统一他们行为的最佳方式是什么。

想到的一些天真的想法(例如让左加法以相反的顺序调用加法来调用 numpy 对事物的解释)似乎可能会导致一些不直观的行为。

我本来希望通过直接查看“add”的 numpy 源代码来更好地了解发生的情况,但是 np.add 的文档页面没有像其他人一样提供其源代码的链接做(如np.atleast_1d)...

这个问题有什么干净的解决方法吗?

---

作为一个附带问题,上面写的加法运算的方式密切反映了我实际课堂中的加法结构,其中的形式看起来有点像这样:

def __add__(self, other):
    if isinstance(other, self.__class__):
        ...
    try:
        ...
    except:
        raise ValueError(...)

我的思考过程是,如果两个对象是同一类型,我知道如何完美处理这个问题,因为我为这个类编写了代码。但是,如果用户尝试添加其他类型,出于我的目的,真正重要的是它具有作为整数的合理解释。所以我最终在“三思而后行”和“更容易请求宽恕而不是许可”之间形成了一些奇怪的混合编码风格,我不确定它是否是亵渎的。这被认为是不好的编码风格吗?

python numpy class addition numpy-ndarray
1个回答
0
投票

Numpy 为此提供了一些 hooks

在这种情况下,您可能想在您的班级上实现

class.__array_ufunc__()
。如果你简单地用
None
定义它,它会引发异常:

__array_ufunc__ = None 

或者,你可以实际实现一些东西:

def __array_ufunc__(self, ufunc, method, *inputs):
    print(f"{ufunc} called with {inputs}") 
    return self

# b + a just 
# prints: <ufunc 'add'> called with (array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), 6)
# and returns itself
© www.soinside.com 2019 - 2024. All rights reserved.