子類化ndarray

介紹

子類化ndarray相對簡單,但與其他Python對象相比,它有一些復(fù)雜性。在這個頁面上,我們解釋了允許你子類化ndarray的機制,以及實現(xiàn)子類的含義。

ndarrays和對象創(chuàng)建

ndarray的子??類化很復(fù)雜,因為ndarray類的新實例可以以三種不同的方式出現(xiàn)。這些是:

  1. 顯式構(gòu)造函數(shù)調(diào)用 - 如 MySubClass(params)。這是Python實例創(chuàng)建的常用途徑。
  2. 查看轉(zhuǎn)換 - 將現(xiàn)有的ndarray轉(zhuǎn)換為給定的子類
  3. 模板中的新內(nèi)容 - 從模板實例創(chuàng)建新實例。示例包括從子類化數(shù)組返回切片,從ufuncs創(chuàng)建返回類型以及復(fù)制數(shù)組。有關(guān)更多詳細(xì)信息,請參閱
    從模板創(chuàng)建

最后兩個是ndarrays的特性 - 為了支持?jǐn)?shù)組切片之類的東西。子類化ndarray的復(fù)雜性是由于numpy必須支持后兩種實例創(chuàng)建路徑的機制。

視圖投影

視圖投影 是標(biāo)準(zhǔn)的ndarray機制,通過它您可以獲取任何子類的ndarray,并將該數(shù)組的視圖作為另一個(指定的)子類返回:

>>> import numpy as np
>>> # create a completely useless ndarray subclass
>>> class C(np.ndarray): pass
>>> # create a standard ndarray
>>> arr = np.zeros((3,))
>>> # take a view of it, as our useless subclass
>>> c_arr = arr.view(C)
>>> type(c_arr)
<class 'C'>

從模板創(chuàng)建

當(dāng)numpy發(fā)現(xiàn)它需要從模板實例創(chuàng)建新實例時,ndarray子類的新實例也可以通過與視圖投影非常相似的機制來實現(xiàn)。
這個情況的最明顯的時候是你正為子類數(shù)組切片的時候。例如:

>>> v = c_arr[1:]
>>> type(v) # the view is of type 'C'
<class 'C'>
>>> v is c_arr # but it's a new instance
False

切片是原始 c_arr 數(shù)據(jù)的 視圖 。因此,當(dāng)我們從ndarray中獲取視圖時,我們返回一個同一類的新ndarray,它指向原始數(shù)據(jù)。

在使用ndarrays時還有其它要點,我們需要這樣的視圖,例如復(fù)制數(shù)組(c_arr.copy()),創(chuàng)建ufunc輸出數(shù)組(參見__array_wrap__用于ufuncs和其他函數(shù)),
以及減少方法(如c_arr.mean()。

視圖投影與從模板創(chuàng)建的關(guān)系

這些路徑都使用相同的機器。我們在這里進(jìn)行區(qū)分,因為它們會為您的方法帶來不同的輸入。具體來說,
視圖投影意味著您已從ndarray的任何潛在子類創(chuàng)建了數(shù)組類型的新實例。
從模板創(chuàng)建意味著您已從預(yù)先存在的實例創(chuàng)建了類的新實例,例如,允許您跨特定于您的子類的屬性進(jìn)行復(fù)制。

子類化的含義

如果我們將 ndarray 子類化,我們不僅需要處理數(shù)組類型的顯式構(gòu)造,還需要處理視圖投影
從模板創(chuàng)建。NumPy有這樣的機制,這種機制使子類化略微不標(biāo)準(zhǔn)。

ndarray用于支持視圖和子類中的從模板創(chuàng)建的機制有兩個方面。

第一種是使用該ndarray.__new__方法進(jìn)行對象初始化的主要工作,而不是更常用的__init__
方法。第二個是使用該__array_finalize__方法在模板創(chuàng)建視圖和新實例后允許子類清理。

一個簡短的Python入門__new____init__

__new__是一個標(biāo)準(zhǔn)的Python方法,如果存在,__init__在我們創(chuàng)建類實例之前調(diào)用它。
有關(guān)更多詳細(xì)信息,請參閱python __new__ 文檔

。

例如,請考慮以下Python代碼:

class C(object):
    def __new__(cls, *args):
        print('Cls in __new__:', cls)
        print('Args in __new__:', args)
        # The `object` type __new__ method takes a single argument.
        return object.__new__(cls)

    def __init__(self, *args):
        print('type(self) in __init__:', type(self))
        print('Args in __init__:', args)

它的意思是我們將會得到:

>>> c = C('hello')
Cls in __new__: <class 'C'>
Args in __new__: ('hello',)
type(self) in __init__: <class 'C'>
Args in __init__: ('hello',)

當(dāng)我們調(diào)用時C('hello'),該__new__方法獲得自己的類作為第一個參數(shù),并傳遞參數(shù),即字符串
'hello'。在python調(diào)用之后__new__,它通常(見下文)調(diào)用我??們的__init__方法,輸出__new__為第一個參數(shù)(現(xiàn)在是一個類實例),以及后面?zhèn)鬟f的參數(shù)。

如您所見,對象可以在__new__
方法或__init__方法中初始化,或者兩者兼而有之,實際上ndarray沒有__init__方法,因為所有初始化都是在__new__方法中完成的。

為什么要使用__new__而不僅僅是平常__init__?因為在某些情況下,對于ndarray,我們希望能夠返回其他類的對象??紤]以下:

class D(C):
    def __new__(cls, *args):
        print('D cls is:', cls)
        print('D args in __new__:', args)
        return C.__new__(C, *args)

    def __init__(self, *args):
        # we never get here
        print('In D __init__')

意思是:

>>> obj = D('hello')
D cls is: <class 'D'>
D args in __new__: ('hello',)
Cls in __new__: <class 'C'>
Args in __new__: ('hello',)
>>> type(obj)
<class 'C'>

定義C與之前相同,但是,對于D,該
__new__方法返回類的實例C而不是
D。請注意,該__init__方法D不會被調(diào)用。通常,當(dāng)__new__方法返回類的對象而不是定義__init__
它的類時,不調(diào)用該類的方法。

這就是ndarray類的子類如何能夠返回保留類類型的視圖。在進(jìn)行視圖時,標(biāo)準(zhǔn)的ndarray機器會創(chuàng)建新的ndarray對象,例如:

obj = ndarray.__new__(subtype, shape, ...

subdtype子類在哪里。因此,返回的視圖與子類屬于同一類,而不是類ndarray。

這解決了返回相同類型的視圖的問題,但是現(xiàn)在我們有了一個新的問題。
ndarray的機制可以這樣設(shè)置類,在其用于獲取視圖的標(biāo)準(zhǔn)方法中,
但是ndarray __new__ 方法不知道我們在自己的 __new__ 方法中為了設(shè)置屬性所做的任何事情,
等等。(拋開-為什么不調(diào)用 obj = subdtype._new_(... 然后?。因為我們可能沒有具有相同調(diào)用簽名的 __new__ 方法)。

__array_finalize__ 的作用

__array_finalize__ 是numpy提供的機制,允許子類處理創(chuàng)建新實例的各種方法。

請記住,子類實例可以通過以下三種方式實現(xiàn):

  1. 顯式的調(diào)用構(gòu)造函數(shù)(obj = MySubClass(params))。 這將調(diào)用 MySubClass.__ new__ 的常用序列,然后(如果存在)MySubClass.__init__。
  2. 視圖投影
  3. 從模板創(chuàng)建

我們的 MySubClass.__new__ 方法只在顯式構(gòu)造函數(shù)調(diào)用的情況下被調(diào)用,
所以我們不能依賴 MySubClass.__new__MySubClass.__init__ 來處理視圖轉(zhuǎn)換和從模板創(chuàng)建。事實證明,
MySubClass.__array_finalize__ 確實為對象創(chuàng)建的所有三種方法都被調(diào)用,所以這是我們的對象創(chuàng)建內(nèi)務(wù)通常去的地方。

  • 對于顯式構(gòu)造函數(shù)調(diào)用,我們的子類需要創(chuàng)建自己的類的新ndarray實例。
    在實踐中,這意味著我們作為代碼的作者將需要調(diào)用 ndarray.__new__(MySubClass,...), 一個類層次結(jié)構(gòu)調(diào)用 super(MySubClass, cls).__new__(cls, ...) ,
    或者查看現(xiàn)有數(shù)組的轉(zhuǎn)換(見下文)
  • 對于視圖轉(zhuǎn)換和從模板創(chuàng)建 ndarray.__new__(MySubClass,...,在C級別調(diào)用等效項。

對于上述三種實例創(chuàng)建方法,__array_finalize__ 接收的參數(shù)不同。

以下代碼允許我們查看調(diào)用序列和參數(shù):

import numpy as np

class C(np.ndarray):
    def __new__(cls, *args, **kwargs):
        print('In __new__ with class %s' % cls)
        return super(C, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        # in practice you probably will not need or want an __init__
        # method for your subclass
        print('In __init__ with class %s' % self.__class__)

    def __array_finalize__(self, obj):
        print('In array_finalize:')
        print('   self type is %s' % type(self))
        print('   obj type is %s' % type(obj))

現(xiàn)在:

>>> # Explicit constructor
>>> c = C((10,))
In __new__ with class <class 'C'>
In array_finalize:
   self type is <class 'C'>
   obj type is <type 'NoneType'>
In __init__ with class <class 'C'>
>>> # View casting
>>> a = np.arange(10)
>>> cast_a = a.view(C)
In array_finalize:
   self type is <class 'C'>
   obj type is <type 'numpy.ndarray'>
>>> # Slicing (example of 從模板創(chuàng)建)
>>> cv = c[:1]
In array_finalize:
   self type is <class 'C'>
   obj type is <class 'C'>

簽名__array_finalize__是:

def __array_finalize__(self, obj):

可以看到進(jìn)行的super調(diào)用
ndarray.__new__傳遞__array_finalize__了我們自己的class(self)的新對象以及從中獲取視圖的對象(obj)。從上面的輸出可以看出,self它總是一個新創(chuàng)建的子類實例,并且obj
三種實例創(chuàng)建方法的類型不同:

  • 從顯式構(gòu)造函數(shù)調(diào)用時,objNone
  • 從視圖轉(zhuǎn)換中調(diào)用時,obj可以是ndarray的任何子類的實例,包括我們自己的子類。
  • 在從模板創(chuàng)建中調(diào)用時,obj是我們自己的子類的另一個實例,我們可能會用它來更新新self實例。

因為__array_finalize__是唯一始終看到正在創(chuàng)建新實例的方法,所以在其他任務(wù)中填充新對象屬性的實例默認(rèn)值是合理的。

通過一個例子,這可能更清楚。

簡單示例 —— 向ndarray添加額外屬性

import numpy as np

class InfoArray(np.ndarray):

    def __new__(subtype, shape, dtype=float, buffer=None, offset=0,
                strides=None, order=None, info=None):
        # Create the ndarray instance of our type, given the usual
        # ndarray input arguments.  This will call the standard
        # ndarray constructor, but return an object of our type.
        # It also triggers a call to InfoArray.__array_finalize__
        obj = super(InfoArray, subtype).__new__(subtype, shape, dtype,
                                                buffer, offset, strides,
                                                order)
        # set the new 'info' attribute to the value passed
        obj.info = info
        # Finally, we must return the newly created object:
        return obj

    def __array_finalize__(self, obj):
        # ``self`` is a new object resulting from
        # ndarray.__new__(InfoArray, ...), therefore it only has
        # attributes that the ndarray.__new__ constructor gave it -
        # i.e. those of a standard ndarray.
        #
        # We could have got to the ndarray.__new__ call in 3 ways:
        # From an explicit constructor - e.g. InfoArray():
        #    obj is None
        #    (we're in the middle of the InfoArray.__new__
        #    constructor, and self.info will be set when we return to
        #    InfoArray.__new__)
        if obj is None: return
        # From view casting - e.g arr.view(InfoArray):
        #    obj is arr
        #    (type(obj) can be InfoArray)
        # From 從模板創(chuàng)建 - e.g infoarr[:3]
        #    type(obj) is InfoArray
        #
        # Note that it is here, rather than in the __new__ method,
        # that we set the default value for 'info', because this
        # method sees all creation of default objects - with the
        # InfoArray.__new__ constructor, but also with
        # arr.view(InfoArray).
        self.info = getattr(obj, 'info', None)
        # We do not need to return anything






使用該對象如下所示:

>>> obj = InfoArray(shape=(3,)) # explicit constructor
>>> type(obj)
<class 'InfoArray'>
>>> obj.info is None
True
>>> obj = InfoArray(shape=(3,), info='information')
>>> obj.info
'information'
>>> v = obj[1:] # 從模板創(chuàng)建 - here - slicing
>>> type(v)
<class 'InfoArray'>
>>> v.info
'information'
>>> arr = np.arange(10)
>>> cast_arr = arr.view(InfoArray) # view casting
>>> type(cast_arr)
<class 'InfoArray'>
>>> cast_arr.info is None
True

這個類不是很有用,因為它與裸ndarray對象具有相同的構(gòu)造函數(shù),包括傳入緩沖區(qū)和形狀等等。我們可能更喜歡構(gòu)造函數(shù)能夠從通常的numpy調(diào)用中獲取已經(jīng)形成的ndarray np.array并返回一個對象。

稍微更現(xiàn)實的例子 —— 添加到現(xiàn)有數(shù)組的屬性

這是一個類,它采用已經(jīng)存在的標(biāo)準(zhǔn)ndarray,轉(zhuǎn)換為我們的類型,并添加一個額外的屬性。

import numpy as np

class RealisticInfoArray(np.ndarray):

    def __new__(cls, input_array, info=None):
        # Input array is an already formed ndarray instance
        # We first cast to be our class type
        obj = np.asarray(input_array).view(cls)
        # add the new attribute to the created instance
        obj.info = info
        # Finally, we must return the newly created object:
        return obj

    def __array_finalize__(self, obj):
        # see InfoArray.__array_finalize__ for comments
        if obj is None: return
        self.info = getattr(obj, 'info', None)

所以:

>>> arr = np.arange(5)
>>> obj = RealisticInfoArray(arr, info='information')
>>> type(obj)
<class 'RealisticInfoArray'>
>>> obj.info
'information'
>>> v = obj[1:]
>>> type(v)
<class 'RealisticInfoArray'>
>>> v.info
'information'

__array_ufunc__ 對于ufuncs

版本1.13中的新功能。

子類可以覆蓋在通過覆蓋默認(rèn)ndarray.__array_ufunc__方法對其執(zhí)行numpy ufuncs時發(fā)生的情況。執(zhí)行此方法 而不是 ufunc,并且應(yīng)該返回操作的結(jié)果,
或者NotImplemented

如果未執(zhí)行所請求的操作。

簽名 __array_ufunc__ 是:

def __array_ufunc__(ufunc, method, *inputs, **kwargs):

- *ufunc* is the ufunc object that was called.
- *method* is a string indicating how the Ufunc was called, either
  ``"__call__"`` to indicate it was called directly, or one of its
  :ref:`methods<ufuncs.methods>`: ``"reduce"``, ``"accumulate"``,
  ``"reduceat"``, ``"outer"``, or ``"at"``.
- *inputs* is a tuple of the input arguments to the ``ufunc``
- *kwargs* contains any optional or keyword arguments passed to the
  function. This includes any ``out`` arguments, which are always
  contained in a tuple.

典型的實現(xiàn)將轉(zhuǎn)換作為一個人自己的類的實例的任何輸入或輸出,使用所有內(nèi)容傳遞給超類
super(),并最終在可能的反向轉(zhuǎn)換后返回結(jié)果。舉例來說,來自測試案例采取
test_ufunc_override_with_supercore/tests/test_umath.py,如下。

input numpy as np

class A(np.ndarray):
    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
        args = []
        in_no = []
        for i, input_ in enumerate(inputs):
            if isinstance(input_, A):
                in_no.append(i)
                args.append(input_.view(np.ndarray))
            else:
                args.append(input_)

        outputs = kwargs.pop('out', None)
        out_no = []
        if outputs:
            out_args = []
            for j, output in enumerate(outputs):
                if isinstance(output, A):
                    out_no.append(j)
                    out_args.append(output.view(np.ndarray))
                else:
                    out_args.append(output)
            kwargs['out'] = tuple(out_args)
        else:
            outputs = (None,) * ufunc.nout

        info = {}
        if in_no:
            info['inputs'] = in_no
        if out_no:
            info['outputs'] = out_no

        results = super(A, self).__array_ufunc__(ufunc, method,
                                                 *args, **kwargs)
        if results is NotImplemented:
            return NotImplemented

        if method == 'at':
            if isinstance(inputs[0], A):
                inputs[0].info = info
            return

        if ufunc.nout == 1:
            results = (results,)

        results = tuple((np.asarray(result).view(A)
                         if output is None else output)
                        for result, output in zip(results, outputs))
        if results and isinstance(results[0], A):
            results[0].info = info

        return results[0] if len(results) == 1 else results

所以,這個類實際上并沒有做任何有趣的事情:它只是將它自己的任何實例轉(zhuǎn)換為常規(guī)的ndarray(否則,我們將獲得無限遞歸?。⑻砑右粋€info字典,告訴它轉(zhuǎn)換了哪些輸入和輸出。因此,例如,

>>> a = np.arange(5.).view(A)
>>> b = np.sin(a)
>>> b.info
{'inputs': [0]}
>>> b = np.sin(np.arange(5.), out=(a,))
>>> b.info
{'outputs': [0]}
>>> a = np.arange(5.).view(A)
>>> b = np.ones(1).view(A)
>>> c = a + b
>>> c.info
{'inputs': [0, 1]}
>>> a += b
>>> a.info
{'inputs': [0, 1], 'outputs': [0]}

請注意,另一種方法是使用 getattr(ufunc,method)(*input,*kwargs) 而不是 super call。
對于本例,結(jié)果是相同的,但如果另一個操作數(shù)也定義了 __array_ufunc__ ,則會有所不同。
例如,假設(shè)我們評估 np.add(a,b),其中b是具有覆蓋的另一個類B的實例。
如果在示例中使用superndarray.__array_ufunc__ 會注意到b具有覆蓋,這意味著它不能計算結(jié)果本身。
因此,它將返回 NotImplemented ,我們的類A也將如此。
然后,控制權(quán)將傳遞給 bb 要么知道如何處理我們并產(chǎn)生結(jié)果,要么不知道并返回 NotImplemented,從而引發(fā) TypeError。

相反,如果我們用 getattr(ufunc,method) 替換 super call,我們將有效地執(zhí)行 np.add(a.view(np.ndarray),b)。
同樣,將調(diào)用 B.__array_ufunc__,但現(xiàn)在它將 ndarray 視為另一個參數(shù)。
很可能,它將知道如何處理此問題,并將B類的新實例返回給我們。
我們的示例類沒有設(shè)置為處理此問題,但如果例如使用 __array_ufunc__ 重新實現(xiàn) MaskedArray,這可能是最好的方法。

最后要注意:如果 super 路由適合給定的類,使用它的一個優(yōu)點是它有助于構(gòu)造類層次結(jié)構(gòu)。
例如,假設(shè)我們的其他類B在其 __array_ufunc__ 實現(xiàn)中也使用了 super
并且我們創(chuàng)建了一個依賴于它們的類 C,即 calss C(A, B)(為簡單起見,沒有另一個 __array_ufunc__ 覆蓋)。
然后,C實例上的任何ufunc都將傳遞給 A.__ array_ufunc__,
A 中的超級調(diào)用將轉(zhuǎn)到 B.__ array_ufunc__,
而 B 中的 super call 將轉(zhuǎn)到 ndarray.__array_ufunc__ ,從而允許 AB 協(xié)作。

__array_wrap__用于ufuncs和其他函數(shù)

在numpy 1.13之前,ufuncs的行為只能使用 __array_wrap____array_prepare__ 來調(diào)優(yōu)。
這兩個允許一個更改ufunc的輸出類型,但與 __array_ufunc__ 相反,不允許對輸入進(jìn)行任何更改。
希望最終淘汰這些功能,但是其他 numpy 函數(shù)和方法也使用 __array_wrap__ ,例如 squeeze,因此目前仍然需要完整的功能。

從概念上講,__array_wrap__ “包裝動作” 的意義是允許子類設(shè)置返回值的類型并更新屬性和元數(shù)據(jù)。
讓我們用一個例子來說明它是如何工作的。首先,我們返回到更簡單的Example子類,但具有不同的名稱和一些print語句:

import numpy as np

class MySubClass(np.ndarray):

    def __new__(cls, input_array, info=None):
        obj = np.asarray(input_array).view(cls)
        obj.info = info
        return obj

    def __array_finalize__(self, obj):
        print('In __array_finalize__:')
        print('   self is %s' % repr(self))
        print('   obj is %s' % repr(obj))
        if obj is None: return
        self.info = getattr(obj, 'info', None)

    def __array_wrap__(self, out_arr, context=None):
        print('In __array_wrap__:')
        print('   self is %s' % repr(self))
        print('   arr is %s' % repr(out_arr))
        # then just call the parent
        return super(MySubClass, self).__array_wrap__(self, out_arr, context)

我們在新數(shù)組的實例上運行ufunc:

>>> obj = MySubClass(np.arange(5), info='spam')
In __array_finalize__:
   self is MySubClass([0, 1, 2, 3, 4])
   obj is array([0, 1, 2, 3, 4])
>>> arr2 = np.arange(5)+1
>>> ret = np.add(arr2, obj)
In __array_wrap__:
   self is MySubClass([0, 1, 2, 3, 4])
   arr is array([1, 3, 5, 7, 9])
In __array_finalize__:
   self is MySubClass([1, 3, 5, 7, 9])
   obj is MySubClass([0, 1, 2, 3, 4])
>>> ret
MySubClass([1, 3, 5, 7, 9])
>>> ret.info
'spam'

注意,ufunc(np.add) 調(diào)用了 __array_WRAP__ 方法,參數(shù) self 作為 obj,out_arr作為加法的(ndarray)結(jié)果。
反過來,默認(rèn) __array_wrap__(ndarray._array_warp__) 已將結(jié)果強制轉(zhuǎn)換為類 MySubClass,并調(diào)用 __array_finalize__ - 因此復(fù)制了info屬性。這一切都發(fā)生在C級。

但是,我們可以做任何我們想要的事情:

class SillySubClass(np.ndarray):

    def __array_wrap__(self, arr, context=None):
        return 'I lost your data'
>>> arr1 = np.arange(5)
>>> obj = arr1.view(SillySubClass)
>>> arr2 = np.arange(5)
>>> ret = np.multiply(obj, arr2)
>>> ret
'I lost your data'

因此,通過__array_wrap__為我們的子類定義一個特定的方法,我們可以調(diào)整ufuncs的輸出。
__array_wrap__方法需要self,然后是一個參數(shù) - 這是ufunc的結(jié)果 - 和一個可選的參數(shù) 上下文 。
ufuncs 將此參數(shù)作為 3 元素元組返回:( ufunc的名稱,ufunc的參數(shù),ufunc的域),
但不是由其他numpy函數(shù)設(shè)置的。但是,如上所述,可以做其他事情,__array_wrap__應(yīng)該返回其包含類的實例。
請參閱 masked 數(shù)組子類以獲取實現(xiàn)。

除了 __array_wrap__ 在ufunc 之外調(diào)用之外,
還有一個 __array_prepare__ 方法在創(chuàng)建輸出數(shù)組之后但在執(zhí)行任何計算之前調(diào)用ufunc。
默認(rèn)實現(xiàn)除了通過數(shù)組之外什么都不做。
__array_prepare__ 不應(yīng)嘗試訪問數(shù)組數(shù)據(jù)或調(diào)整數(shù)組大小,
它用于設(shè)置輸出數(shù)組類型,更新屬性和元數(shù)據(jù),以及根據(jù)計算開始之前可能需要的輸入執(zhí)行任何檢查。
比如__array_wrap__,__array_prepare__必須返回一個ndarray或其子類或引發(fā)錯誤。

額外的坑 —— 自定義的 __del__ 方法和 ndarray.base

ndarray解決的問題之一是跟蹤ndarray的內(nèi)存所有權(quán)及其視圖。
考慮這樣的情況,我們已經(jīng)創(chuàng)建了ndarray,arr 并使用 v = arr[1:]獲取了一個切片。
這兩個對象看的是相同的內(nèi)存。NumPy使用base屬性跟蹤特定數(shù)組或視圖的數(shù)據(jù)來自何處:

>>> # A normal ndarray, that owns its own data
>>> arr = np.zeros((4,))
>>> # In this case, base is None
>>> arr.base is None
True
>>> # We take a view
>>> v1 = arr[1:]
>>> # base now points to the array that it derived from
>>> v1.base is arr
True
>>> # Take a view of a view
>>> v2 = v1[1:]
>>> # base points to the view it derived from
>>> v2.base is v1
True

一般來說,如果數(shù)組擁有自己的內(nèi)存,
就像arr在這種情況下那樣,
那么arr.base 將是None - 有一些例外 -—— 請參閱numpy書了解更多細(xì)節(jié)。

base屬性可用于判斷我們是否有視圖或原始數(shù)組。
如果我們需要知道在刪除子類數(shù)組時是否進(jìn)行某些特定的清理,這反過來會很有用。
例如,如果刪除原始數(shù)組,我們可能只想進(jìn)行清理,而不是視圖。有關(guān)如何工作的示例,請查看 numpy.core 中的 memmap 類。

子類和下游兼容性

當(dāng)子類化 ndarray 或創(chuàng)建模仿 ndarray 接口的 duck-types 時,
您的任務(wù)是決定您的API與numpy的API將如何對齊。
為方便起見,許多具有相應(yīng)ndarray方法(例如,sum,mean,take,reshape)的Numpy函數(shù)通過檢查函數(shù)的第一個參數(shù)是否具有同名的方法來工作。
如果存在,則調(diào)用該方法,而不是將參數(shù)強制到numpy數(shù)組。

例如,如果您希望子類或 duck-type 與 numpy 的 sum 函數(shù)兼容,則此對象sum方法的方法簽名應(yīng)如下所示:

def sum(self, axis=None, dtype=None, out=None, keepdims=False):
...

這是 np.sum 的完全相同的方法簽名,
所以現(xiàn)在如果用戶在這個對象上調(diào)用 np.sum,numpy 將調(diào)用該對象自己的 sum 方法,
并在簽名中傳遞上面枚舉的這些參數(shù),并且不會引發(fā)錯誤,因為簽名彼此完全兼容。

但是,如果您決定偏離此簽名并執(zhí)行以下操作:

def sum(self, axis=None, dtype=None):
...

此對象不再兼容,np.sum因為如果調(diào)用np.sum,它將傳遞意外的參數(shù),outkeepdims導(dǎo)致引發(fā) TypeError。

如果你希望保持與 numpy 及其后續(xù)版本(可能添加新的關(guān)鍵字參數(shù))的兼容性,
但又不想顯示所有numpy的參數(shù),那么你的函數(shù)的簽名應(yīng)該接受**kwargs。例如:

def sum(self, axis=None, dtype=None, **unused_kwargs):
...

此對象現(xiàn)在再次與 np.sum 兼容,因為任何無關(guān)的參數(shù)(即不是 axisdtype 的關(guān)鍵字)都將隱藏在 *unused_kwargs 參數(shù)中。

作者:柯廣的網(wǎng)絡(luò)日志 ? 子類化ndarray


微信公眾號:Java大數(shù)據(jù)與數(shù)據(jù)倉庫