问题描述
假设我有一个不在我控制范围内的类 A
(numpy.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
解决方法
从 iadd
到 radd
的委托可能很复杂
看一个简单的例子。
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.A
是 view
,A+=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
的类型 D
(a += d
是 d
的实例),那么让 D
提供一种方法来转换将 B
的实例转换为 B
的实例。
D