【问题标题】:Array and __rmul__ operator in Python NumpyPython Numpy 中的数组和 __rmul__ 运算符
【发布时间】:2018-12-12 21:53:16
【问题描述】:

在一个项目中,我创建了一个类,我需要在这个新类和一个真实矩阵之间进行运算,所以我像这样重载了__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的类型感觉如何?

标签: python arrays numpy


【解决方案1】:

行为是预期的。

首先你必须了解像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__ 方法被调用的地方,因为数组中的数字在其 __mul__ 为时返回 NotImplemented使用 foo 参数调用。

显然,如果您将参数的顺序更改为初始乘法,您的 __mul__ 方法会立即被调用,您不会遇到任何问题。

所以,为了回答您的问题,处理此问题的一种方法是让 foo 继承自 ndarray,以便适用第二条规则:

class foo(np.ndarray):
    def __new__(cls):
       # you must implement __new__
    # code as before

警告subclassing ndarray isn't straightforward。 此外,您可能还有其他副作用,因为现在您的课程是 ndarray

【讨论】:

  • numpy 通过什么逻辑决定foo 实例是标量的?
  • @wim 如果它不是一个数组,它就是一个标量。就这么简单。
【解决方案2】:

你可以在你的类中定义__numpy_ufunc__ 函数。它甚至可以在没有子类化np.ndarray 的情况下工作。你可以找到文档here

以下是基于您的案例的示例:

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]]

有效!

【讨论】:

【解决方案3】:

我无法像 Bakuriu 那样准确地解释根本问题,但可能还有另一种解决方案。

您可以通过定义 __array_priority__ 来强制 numpy 使用您的评估方法。正如 numpy 文档中的 here 所解释的那样。

在您的情况下,您必须将类定义更改为:

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

【讨论】:

  • 请注意,这将在未来被弃用。 (source) 正确的做法是Firman' s solution
  • @Alexis 我应该删除我的答案还是在开头写一个粗体警告?
  • 我不确定。您的回答可能对某些人仍然有用,所以我认为标准的方法是让赞成票和反对票运作。 :-)
猜你喜欢
  • 2021-05-11
  • 2021-03-23
  • 1970-01-01
  • 1970-01-01
  • 2014-09-05
  • 1970-01-01
  • 2012-08-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多