Python Numpy中的数组和__rmul__运算符

8
在一个项目中,我创建了一个类,并且我需要在这个新类和一个实际矩阵之间进行操作,所以我重载了__rmul__函数,如下所示。
class foo(object):

    aarg = 0

    def __init__(self):
        self.aarg = 1


    def __rmul__(self,A):
        print(A)
        return 0

    def __mul__(self,A):
        print(A)
        return 0

但是当我调用它时,结果并不是我预期的。
A = [[i*j for i in np.arange(2)  ] for j in np.arange(3)]
A = np.array(A)
R = foo()
C =  A * R

输出:

0
0
0
1
0
2

看起来该函数被调用了6次,每个元素都调用了一次。

相反,__mul__函数工作得非常好。

C = R * A

输出:

[[0 0]
 [0 1]
 [0 2]]

如果A不是一个数组,而只是一个列表的列表,两种方式都可以正常工作。
A = [[i*j for i in np.arange(2)  ] for j in np.arange(3)]
R = foo()
C =  A * R
C = R * A

输出

[[0, 0], [0, 1], [0, 2]]
[[0, 0], [0, 1], [0, 2]]

我希望我的 __rmul__ 函数也能用于数组上(我的原始乘法函数不可交换)。我该如何解决?


你对检查 A 的类型有何感觉? - John Perry
3个回答

7

这种行为是预期的。

首先,您需要了解像x*y这样的操作实际上是如何执行的。Python解释器首先尝试计算x.__mul__(y)。如果此调用返回NotImplemented,则它将然后尝试计算y.__rmul__(x)。除非yx类型的适当子类,在这种情况下,解释器将首先考虑y.__rmul__(x),然后再考虑x.__mul__(y)

现在发生的情况是numpy根据参数是标量还是数组而对待参数不同。

处理数组时,*表示逐元素相乘,而标量乘法将给定标量乘以数组的所有条目。

在您的情况下,foo()被numpy视为标量,因此numpy将数组的所有元素乘以foo。此外,由于numpy不知道类型foo,因此返回一个具有dtype=object的数组,因此返回的对象是:

array([[0, 0],
       [0, 0],
       [0, 0]], dtype=object)

注意:当你尝试计算乘积时,numpy的数组不会返回NotImplemented,因此解释器调用了numpy的数组__mul__方法,该方法执行标量乘法。此时,numpy将尝试将数组的每个条目与你的“标量”foo()相乘,这时就会调用你的__rmul__方法,因为数组中的数字在其调用带有foo参数的__mul__时会返回NotImplemented
显然,如果你改变初始乘法的参数顺序,就会立即调用你的__mul__方法,这样就不会有任何问题。
因此,回答你的问题,处理这种情况的一种方法是让foo继承自ndarray,这样第二个规则就适用了。
class foo(np.ndarray):
    def __new__(cls):
       # you must implement __new__
    # code as before

请注意,子类化ndarray并不是一件简单的事情。此外,由于您的类现在是一个ndarray,可能会有其他副作用。


NumPy 通过什么逻辑来确定 foo 实例是标量? - wim
1
@wim 如果它不是一个数组,那就是一个标量。就这么简单。 - Bakuriu

4
您可以在类中定义__numpy_ufunc__函数。即使没有继承np.ndarray也可以使用它。您可以在这里找到它的文档。
以下是基于您的情况的示例:
class foo(object):

    aarg = 0

    def __init__(self):
        self.aarg = 1

    def __numpy_ufunc__(self, *args):
        pass

    def __rmul__(self,A):
        print(A)
        return 0

    def __mul__(self,A):
        print(A)
        return 0

如果我们尝试一下,

A = [[i*j for i in np.arange(2)  ] for j in np.arange(3)]
A = np.array(A)
R = foo()
C =  A * R

输出:

[[0 0]
 [0 1]
 [0 2]]

它可以工作!


2
__numpy_ufunc__ 现在称为 __array_ufunc__,且具有略微不同的行为,请参见 https://docs.scipy.org/doc/numpy-1.13.0/neps/ufunc-overrides.html。 - hertzsprung

3

我不能像Bakuriu一样精确地解释底层问题,但可能有其他解决方案。

您可以通过定义__array_priority__来强制numpy使用您的评估方法。如numpy文档中所述

在您的情况下,您需要更改类定义为:

MAGIC_NUMBER = 15.0
# for the necessary lowest values of MAGIC_NUMBER look into the numpy docs
class foo(object):
    __array_priority__ = MAGIC_NUMBER
    aarg = 0

    def __init__(self):
        self.aarg = 1


    def __rmul__(self,A):
        print(A)
        return 0

    def __mul__(self,A):
        print(A)
        return 0

请注意,此功能将在未来被弃用。正确的方法是使用Firman的解决方案 - Alexis
@Alexis 我应该删除我的回答还是在开头写一个粗体警告? - mcocdawc
我不确定。你的答案可能对某些人仍然有用,所以我认为标准做法是让赞和踩起作用。 :-) - Alexis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接