在 Python 中从右侧覆盖 __iadd__

问题描述

假设我有一个不在我控制范围内的类 Anumpy.ndarray 是特定的应用程序),而在我的控制范围内有一个B (scipy.sparse.coo_matrix)。我想在不触及 B 类的情况下实现将 A 就地添加A

这可能吗?这是一个坏主意吗?如果这在一般情况下是不可能的,是否可以专门使用 numpy 数组?

考虑一个具体的例子:

class A:
   foo = 0

   def __iadd__(self,other):
       print("Avoid calling this function.")
       return self

class B:
   def __add__(self,other):
       if isinstance(other,A):
           other.foo += 1
           return other

   __radd__ = __add__

   # Modify this class to make assertion below pass

a1,a2 = A(),A()
a1 += B()
a2 = a2 + B()
assert a1.foo == a2.foo == 1,"How to make this work"

编辑:实际应用。

将稀疏坐标矩阵就地添加到密集的 numpy 数组具有有效的实现:

from time import time

import numpy as np
from scipy import sparse

N = 1000
a = np.zeros((N,N))
b = sparse.identity(N,format="coo")

t_slow = -time()
a += b  # Want to override,converts b to array—slow!
t_slow += time()

t_fast = -time()
a[b.row,b.col] += b.data  # Desired implementation
t_fast += time()
print(f"{t_slow=:.2}s,{t_fast=:.2}s")
# t_slow=0.0017s,t_fast=0.00024s

解决方法

iaddradd 的委托可能很复杂

看一个简单的例子。

In [237]: M=sparse.coo_matrix(np.eye(3))
In [238]: M
Out[238]: 
<3x3 sparse matrix of type '<class 'numpy.float64'>'
    with 3 stored elements in COOrdinate format>
In [239]: A = np.zeros((3,3))
In [240]: A
Out[240]: 
array([[0.,0.,0.],[0.,0.]])
In [241]: A + M         # a conventional add - makes a np.matrix
Out[241]: 
matrix([[1.,1.,1.]])
In [242]: A += M
In [243]: A             # this too is np.matrix!
Out[243]: 
matrix([[1.,1.]])

但是如果我把稀疏转换成加法,结果是数组:

In [244]: A = np.zeros((3,3))
In [246]: A += M.A
In [247]: A
Out[247]: 
array([[1.,1.]])

我怀疑(但不要引用我的话)

A.__iadd__(M) =>
M.__radd__(A) =>
M.__add__(A) =>
M._add_dense(A) =>

def _add_dense(self,other):
    if other.shape != self.shape:
        raise ValueError('Incompatible shapes ({} and {})'
                         .format(self.shape,other.shape))
    dtype = upcast_char(self.dtype.char,other.dtype.char)
    result = np.array(other,dtype=dtype,copy=True)
    fortran = int(result.flags.f_contiguous)
    M,N = self.shape
    coo_todense(M,N,self.nnz,self.row,self.col,self.data,result.ravel('A'),fortran)
    return matrix(result,copy=False)

这可以解释 np.matrix 返回。通常我不希望 A+=... 改变 A 形状、类或数据类型。但看起来 sparse.coo 控制了。我没看过那个coo_todense

还有一些其他证据。 A+=M 可以更改数据类型,而 A+=M.A 不能。 A+=M.AviewA+=M 不是。

因此,您或许可以修改 sparse.coo_matrix 方法以根据需要执行操作。事实上,我可以想象为这种稀疏行为提交一个错误问题。它不仅速度较慢,而且会以非 numpy 的标准方式更改 A

但为什么不只编写一个简单的实用程序函数,而不用覆盖 += 呢?

In [249]:     def add_to_dense(a,b):
     ...:         a[b.row,b.col] += b.data
     ...: 
In [250]: add_to_dense(A,M)
In [251]: A
Out[251]: 
array([[2.,2.,2.]])
,

如果您希望 a += b 起作用,请定义一种显式b 转换为确实与 {{1} 一起工作的值的方法}.否则,您就会为 A.__iadd__ 之类的东西打开大门,就地修改 c = a + b,这可能会导致一些难以发现的错误。

如果存在类型 a 的类型 Da += dd 的实例),那么让 D 提供一种方法来转换将 B 的实例转换为 B 的实例。

D