如何使__add__的numpy重载独立于操作数顺序?

Mah*_*ahé 5 python arrays numpy operator-overloading

在包含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)
Run Code Online (Sandbox Code Playgroud)

A的一个例子:

>>> a = A(np.arange(5))
Run Code Online (Sandbox Code Playgroud)

这按预期工作:

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

这不是:

>>> np.ones(5) + a
array([ 1.,  2.,  3.,  4.,  5.])
Run Code Online (Sandbox Code Playgroud)

即使这很好:

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

第二个例子中发生的事情是根本不调用radd,而是调用__add__np.ones(5)中的numpy方法.

我从这篇文章中尝试了一些建议,但__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
Run Code Online (Sandbox Code Playgroud)

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

解决方案:除了子类化的M4rtini解决方案之外,还可以向__array_wrap__A类添加属性(以避免子类化).更多这里.根据塞伯格,__array_priority__也可以研究更新的numpy版本(见评论).

Mar*_*ers 6

创建A一个子类,np.ndarrayPython将首先调用您的A.__radd__方法.

object.__radd__文档:

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

通过子类化您的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.])
Run Code Online (Sandbox Code Playgroud)

请研究Subclassing ndarray文档以获取警告和含义.


Mah*_*ahé 1

感谢@M4rtini和@seberg,添加似乎确实__array_wrap__解决了问题:

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

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