我正在编写一个类,其中存储的属性之一在构造函数中被转换为整数。我还重载了左/右加法,其中添加/减去一个整数意味着将此属性移动该整数。原则上,加法在我的班级的所有上下文中都可以通勤,因此作为用户,我不希望左右加法之间有任何差异。
但是,我的代码不同意。我天真地期望 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] 例如,计算表达式|
,其中y是具有x - 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(...)
我的思考过程是,如果两个对象是同一类型,我知道如何完美处理这个问题,因为我为这个类编写了代码。但是,如果用户尝试添加其他类型,出于我的目的,真正重要的是它具有作为整数的合理解释。所以我最终在“三思而后行”和“更容易请求宽恕而不是许可”之间形成了一些奇怪的混合编码风格,我不确定它是否是亵渎的。这被认为是不好的编码风格吗?
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