【问题标题】:How to make numpy overloading of __add__ independent on operand order?如何使 __add__ 的 numpy 重载独立于操作数顺序?
【发布时间】:2014-03-25 11:16:50
【问题描述】:

在包含 numpy 数组作为属性的类中重载运算符时遇到问题。根据操作数的顺序,结果类型将是我的 A 类(期望的行为)或 numpy 数组。如何让它总是返回一个 A 的实例?

例子:

import numpy as np

class A(object):
    """ class overloading a numpy array for addition
    """
    def __init__(self, values):
        self.values = values

    def __add__(self, x):
        """ addition
        """
        x = np.array(x) # make sure input is numpy compatible
        return A(self.values + x)

    def __radd__(self, x):
        """ reversed-order (LHS <-> RHS) addition
        """
        x = np.array(x) # make sure input is numpy compatible
        return A(x + self.values)

    def __array__(self):
        """ so that numpy's array() returns values
        """
        return self.values

    def __repr__(self):
        return "A object: "+repr(self.values)

A 的一个实例:

>>> a = A(np.arange(5))

这按预期工作:

>>> a + np.ones(5)  
A object: array([ 1.,  2.,  3.,  4.,  5.])

这不是:

>>> np.ones(5) + a
array([ 1.,  2.,  3.,  4.,  5.])

尽管这样很好:

>>> list(np.ones(5)) + a
A object: array([ 1.,  2.,  3.,  4.,  5.])

在第二个示例中发生的情况是根本没有调用 radd,而是调用了 np.ones(5) 中的 numpy 方法 __add__

我尝试了来自 this post 的一些建议,但 __array_priority__ 似乎没有任何区别(在 seberg 评论后编辑:至少在 numpy 1.7.1 中,但可以在较新的版本上工作),并且 __set_numeric_ops__ 领导到 Segmentation Fault... 我想我做错了什么。

对上述简单示例有任何建议(同时保留__array__ 属性)?

编辑:我不希望 A 成为 np.ndarray 的子类,因为这会带来我想避免的其他并发症——至少现在是这样。请注意,熊猫似乎已经解决了这个问题:

import pandas as pd
df = pd.DataFrame(np.arange(5)) 
type(df.values + df) is pd.DataFrame  # returns True
isinstance(df, np.ndarray) # returns False

我很想知道这是怎么做到的。

解决方案:除了子类化的M4rtini解决方案外,还可以在A类中添加__array_wrap__属性(避免子类化)。更多here。根据 seberg 的说法,__array_priority__ 也可以在较新的 numpy 版本上工作(见评论)。

【问题讨论】:

  • 如果添加__array__,则添加__array_priority__,不确定是否涵盖旧版本的所有内容。
  • 有什么特殊的方法可以做到这一点吗?如果我只是添加一个类属性__array_priority__ = 1000,并不能解决问题。我也尝试了 0, -1 ...我的 numpy 版本是 1.7.1,这是相当新的,不是吗?
  • 你没走多远,__array_wrap__ 成功了(见下面的答案)
  • 相当新,但还不够我的意思。

标签: python arrays numpy operator-overloading


【解决方案1】:

使A 成为np.ndarray 的子类,Python 将调用您的A.__radd__ 方法首先

来自object.__radd__ documentation

注意:如果右操作数的类型是左操作数类型的子类,并且该子类为操作提供了反射方法,则该方法将在左操作数的非反射方法之前调用。这种行为允许子类覆盖其祖先的操作。

通过子类化你的A 对象确实能够拦截添加:

>>> import numpy as np
>>> class A(np.ndarray):
...     """ class overloading a numpy array for addition
...     """
...     def __init__(self, values):
...         self.values = values
...     def __add__(self, x):
...         """ addition
...         """
...         x = np.array(x) # make sure input is numpy compatible
...         return A(self.values + x)
...     def __radd__(self, x):
...         """ reversed-order (LHS <-> RHS) addition
...         """
...         x = np.array(x) # make sure input is numpy compatible
...         return A(x + self.values)
...     def __array__(self):
...         """ so that numpy's array() returns values
...         """
...         return self.values
...     def __repr__(self):
...         return "A object: "+repr(self.values)
... 
>>> a = A(np.arange(5))
>>> a + np.ones(5)  
A object: array([ 1.,  2.,  3.,  4.,  5.])
>>> np.ones(5) + a
A object: array([ 1.,  2.,  3.,  4.,  5.])

请研究 Subclassing ndarray documenation 以了解警告和含义。

【讨论】:

  • 谢谢,但我实际上希望不必子类化,因为所讨论的类非常大并且会出现不兼容性(此处仅供参考:github.com/perrette/dimarray)。我注意到 pandas DataFrame 确实解决了这个问题,所以我想还有另一种解决方案。
  • @Mahé 我很确定 pandas DataFrame 实际上是 numpy.ndarray 的子类。如果没有,您可以查看 pandas 的源代码,看看他们是如何做到的。
  • @M4rtini 不是...尝试 isinstance(pd.DataFrame(), np.ndarray) 或 issubclass(pd.DataFrame, np.ndarray),它们将返回 False。我想知道是否有一个明显的答案,但如果没有,是的,我将查看源代码,并在这里分享答案。
  • @Mahé 看起来它随着版本 0.13 的 pandas 发生了变化。 source "在 0.13.0 系列中,内部已重构为不再是 ndarray 的子类,而是 NDFrame 的子类,类似于其他 pandas 容器。"
  • 啊,是的,我记得读过。签入代码后,请参阅下面的最终答案。从 numpy 文档中,我认为 __array_wrap__ 仅适用于子类,但它似乎在 __array__ 出现后立即起作用。
【解决方案2】:

感谢@M4rtini 和@seberg,看来添加__array_wrap__ 确实解决了这个问题:

class A(object):
    ...
    def __array_wrap__(self, result):
        return A(result)  # can add other attributes of self as constructor

它似乎在任何 ufunc 操作结束时被调用(它包括数组添加)。这也是 pandas 的做法(在 0.12.0 中,pandas/core/frame.py l. 6020)。

【讨论】:

    【解决方案3】:

    @Martijn Pieters 似乎不起作用,因为对 nparray 进行子类化有一些特殊规则(请参阅 here),包括使用 __new__ 而不是 __init__ 和使用 __array_finalize__

    这是适合我的代码:

    import numpy as np
    
    class Abstract_Array(np.ndarray):
        """ class overloading a numpy array for addition
        """
        def __new__(cls, input_array):
            obj = np.asarray(input_array).view(cls)
            return obj
    
        def __array_finalize__(self, obj):
            return None
    
        def __add__(self, x):
            """ addition
            """
            x = np.array(x) # make sure input is numpy compatible
            return Abstract_Array(addfunc(self,x)) # define your own add function
    
        def __radd__(self, x):
            """ reversed-order (LHS <-> RHS) addition
            """
            x = np.array(x) # make sure input is numpy compatible
            return Abstract_Array(raddfunc(self,x))
    
        def __array__(self):
            """ so that numpy's array() returns values
            """
            return self
    
        def __repr__(self):
            return "Abstract_Array object of shape %s: \n %s" % (str(self.shape), str(self)[:100])
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-19
      • 1970-01-01
      • 1970-01-01
      • 2016-04-21
      • 1970-01-01
      • 2015-05-10
      • 1970-01-01
      相关资源
      最近更新 更多