2014-03-25 23 views
3

我在包含numpy數組的屬性的類中重載運算符時遇到了一個問題。根據操作數的順序,結果類型將是我的類A(所需行爲)或一個numpy數組。如何使它總是返回一個A的實例?如何使__add__的numpy重載獨立於操作數順序?

例子:

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__導致段錯誤..我想我做錯了什麼。

對上述簡單示例有效的任何建議(同時保留__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解決方案,還可以將__array_wrap__屬性添加到類A(以避免子類化)。更多here。根據seberg,__array_priority__也可以在較新的numpy版本上工作(請參閱評論)。

+0

如果添加'__array__',則添加'__array_priority__',不確定是否涵蓋舊版本的所有內容。 – seberg

+0

有沒有特殊的方法來做到這一點?如果我只是添加一個類屬性'__array_priority__ = 1000',它不能解決問題。我也嘗試了0,-1 ...我的numpy版本是1.7.1,這是相當新的,不是嗎? –

+0

你還不是很遠,'__array_wrap__'做了訣竅(見下面的答案) –

回答

5

Anp.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的注意事項和影響。

+0

謝謝,但我實際上希望不必子類化,因爲所討論的類非常大並且會呈現不兼容性(此處僅供參考:https://github.com/perrette/dimarray)。我注意到熊貓DataFrame確實解決了這個問題,所以我想還有另一種解決方案。 –

+0

@Mahé我很確定熊貓的DataFrame實際上是numpy.ndarray的一個子類。如果不是,你可以通過熊貓來源,看看他們是如何做到的。 – M4rtini

+0

@ M4rtini它不是...嘗試isinstance(pd.DataFrame(),np.ndarray)或issubclass(pd.DataFrame,np.ndarray),它們將返回False。我想知道是否有明顯的答案,但如果不是的話,是的,我會查看來源,並在此分享答案。 –

1

由於@ M4rtini和@seberg,似乎加入__array_wrap__確實解決了一個問題:

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

這似乎在任何ufunc操作結束被調用(它包括陣列添加)。這也是熊貓的做法(0.12.0,pandas/core/frame.py l。6020)。