NumPy 子類化 ndarray

2021-09-01 15:09 更新

介紹

對(duì) ndarray 進(jìn)行子類化相對(duì)簡(jiǎn)單,但與其他 Python 對(duì)象相比,它有一些復(fù)雜性。在此頁(yè)面上,我們解釋了允許您對(duì) ndarray 進(jìn)行子類化的機(jī)制,以及實(shí)現(xiàn)子類的含義。

ndarrays 和對(duì)象創(chuàng)建

由于 ndarray 類的新實(shí)例可以通過(guò)三種不同的方式產(chǎn)生,因此子類化 ndarray 很復(fù)雜。這些是:

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

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

查看投射

視圖轉(zhuǎn)換是標(biāo)準(zhǔn)的 ndarray 機(jī)制,您可以通過(guò)它獲取任何子類的 ndarray,并將數(shù)組的視圖作為另一個(gè)(指定的)子類返回:

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

從模板創(chuàng)建新

ndarray 子類的新實(shí)例也可以通過(guò)與View cast非常相似的機(jī)制產(chǎn)生,當(dāng) numpy 發(fā)現(xiàn)它需要從模板實(shí)例創(chuàng)建一個(gè)新實(shí)例時(shí)。這必須發(fā)生的最明顯的地方是當(dāng)您獲取子類數(shù)組的切片時(shí)。例如:

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

切片是原始數(shù)據(jù)的視圖c_arr。因此,當(dāng)我們從 ndarray 中查看時(shí),我們返回一個(gè)新的 ndarray,屬于同一類,指向原始數(shù)據(jù)。

使用 ndarrays 的其他點(diǎn)我們需要這樣的視圖,例如復(fù)制數(shù)組 (?c_arr.copy())、創(chuàng)建 ufunc 輸出數(shù)組(另請(qǐng)參閱__array_wrap__ 以了解 ufuncs 和其他函數(shù))和減少方法(如?c_arr.mean())。

視圖轉(zhuǎn)換和新模板的關(guān)系

這些路徑都使用相同的機(jī)器。我們?cè)谶@里進(jìn)行區(qū)分,因?yàn)樗鼈儠?huì)導(dǎo)致對(duì)您的方法的不同輸入。具體來(lái)說(shuō),?視圖轉(zhuǎn)換意味著您已經(jīng)從 ndarray 的任何潛在子類創(chuàng)建了數(shù)組類型的新實(shí)例。?從模板?創(chuàng)建新實(shí)例意味著您已經(jīng)從預(yù)先存在的實(shí)例創(chuàng)建了類的新實(shí)例,例如,允許您跨子類特定的屬性進(jìn)行復(fù)制。

子類化的含義

如果我們繼承 ndarray,我們不僅需要處理數(shù)組類型的顯式構(gòu)造,還需要處理視圖轉(zhuǎn)換或?從模板創(chuàng)建新的。NumPy 具有執(zhí)行此操作的機(jī)制,正是這種機(jī)制使子類化略微非標(biāo)準(zhǔn)。

ndarray 用于支持子類中的視圖和新模板的機(jī)制有兩個(gè)方面。

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

關(guān)于__new__和的簡(jiǎn)短 Python 入門(mén)__init__

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

例如,考慮以下 Python 代碼:

  1. class C:
  2. def __new__(cls, *args):
  3. print('Cls in __new__:', cls)
  4. print('Args in __new__:', args)
  5. # The `object` type __new__ method takes a single argument.
  6. return object.__new__(cls)
  7. def __init__(self, *args):
  8. print('type(self) in __init__:', type(self))
  9. print('Args in __init__:', args)

這意味著我們得到:

  1. >>> c = C('hello')
  2. Cls in __new__: <class 'C'>
  3. Args in __new__: ('hello',)
  4. type(self) in __init__: <class 'C'>
  5. Args in __init__: ('hello',)

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

可以看到,對(duì)象可以在__new__?方法中初始化,也可以在方法中初始化__init__,或者兩者都可以,而實(shí)際上ndarray是沒(méi)有__init__方法的,因?yàn)樗械某跏蓟际窃?code>__new__方法中完成的。 為什么要使用__new__而不僅僅是通常的__init__?因?yàn)樵谀承┣闆r下,對(duì)于 ndarray,我們希望能夠返回某個(gè)其他類的對(duì)象。考慮以下:

  1. class D(C):
  2. def __new__(cls, *args):
  3. print('D cls is:', cls)
  4. print('D args in __new__:', args)
  5. return C.__new__(C, *args)
  6. def __init__(self, *args):
  7. # we never get here
  8. print('In D __init__')

意思是:

  1. >>> obj = D('hello')
  2. D cls is: <class 'D'>
  3. D args in __new__: ('hello',)
  4. Cls in __new__: <class 'C'>
  5. Args in __new__: ('hello',)
  6. >>> type(obj)
  7. <class 'C'>

的定義C與之前相同,但對(duì)于D,該?__new__方法返回類的實(shí)例C而不是?D。請(qǐng)注意,不會(huì)調(diào)用的__init__方法D。通常,當(dāng)該__new__方法返回定義它的類以外的類的對(duì)象時(shí),__init__?不會(huì)調(diào)用該類的方法。 這就是 ndarray 類的子類如何能夠返回保留類類型的視圖。在查看時(shí),標(biāo)準(zhǔn)的 ndarray 機(jī)制會(huì)使用以下內(nèi)容創(chuàng)建新的 ndarray 對(duì)象:

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

subdtype子類在哪里。因此,返回的視圖與子類屬于同一類,而不是屬于 class?ndarray。 這解決了返回相同類型視圖的問(wèn)題,但現(xiàn)在我們有一個(gè)新問(wèn)題。ndarray 的機(jī)制可以在其用于獲取視圖的標(biāo)準(zhǔn)方法中以這種方式設(shè)置類,但是 ndarray?__new__方法對(duì)我們?cè)谧约旱?code>__new__方法中為設(shè)置屬性所做的一切一無(wú)所知?,等等。(另外 - 為什么不調(diào)用呢?因?yàn)槲覀兛赡軟](méi)有具有相同調(diào)用簽名的方法)。obj?=?subdtype.__new__(...``__new__

__array_finalize__的作用

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

請(qǐng)記住,子類實(shí)例可以通過(guò)以下三種方式產(chǎn)生:

  1. 顯式構(gòu)造函數(shù)調(diào)用 (?)。這將調(diào)用then (如果存在)?的通常序列。obj?=?MySubClass(params)``MySubClass.__new__``MySubClass.__init__
  2. 查看演員表
  3. 從模板創(chuàng)建新

我們的MySubClass.__new__方法只在顯式構(gòu)造函數(shù)調(diào)用的情況下被調(diào)用,所以我們不能依賴MySubClass.__new__或?MySubClass.__init__處理視圖轉(zhuǎn)換和新模板。事實(shí)證明,MySubClass.__array_finalize__?不被調(diào)用對(duì)象創(chuàng)建的所有三種方法,所以這是我們的對(duì)象創(chuàng)建看家一般無(wú)二。

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

__array_finalize__上述三種實(shí)例創(chuàng)建方法接收的參數(shù)不同。 以下代碼允許我們查看調(diào)用序列和參數(shù):

  1. import numpy as np
  2. class C(np.ndarray):
  3. def __new__(cls, *args, **kwargs):
  4. print('In __new__ with class %s' % cls)
  5. return super().__new__(cls, *args, **kwargs)
  6. def __init__(self, *args, **kwargs):
  7. # in practice you probably will not need or want an __init__
  8. # method for your subclass
  9. print('In __init__ with class %s' % self.__class__)
  10. def __array_finalize__(self, obj):
  11. print('In array_finalize:')
  12. print(' self type is %s' % type(self))
  13. print(' obj type is %s' % type(obj))

現(xiàn)在:

  1. >>> # Explicit constructor
  2. >>> c = C((10,))
  3. In __new__ with class <class 'C'>
  4. In array_finalize:
  5. self type is <class 'C'>
  6. obj type is <type 'NoneType'>
  7. In __init__ with class <class 'C'>
  8. >>> # View casting
  9. >>> a = np.arange(10)
  10. >>> cast_a = a.view(C)
  11. In array_finalize:
  12. self type is <class 'C'>
  13. obj type is <type 'numpy.ndarray'>
  14. >>> # Slicing (example of new-from-template)
  15. >>> cv = c[:1]
  16. In array_finalize:
  17. self type is <class 'C'>
  18. obj type is <class 'C'>

的簽名__array_finalize__是:

  1. def __array_finalize__(self, obj):

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

  • 當(dāng)從顯式構(gòu)造函數(shù)調(diào)用時(shí),objNone
  • 當(dāng)從視圖轉(zhuǎn)換調(diào)用時(shí),obj可以是 ndarray 的任何子類的實(shí)例,包括我們自己的。
  • 當(dāng)在 new-from-template 中調(diào)用時(shí),obj是我們自己子類的另一個(gè)實(shí)例,我們可能會(huì)用它來(lái)更新新self實(shí)例。

因?yàn)?code>__array_finalize__它是唯一能始終看到新實(shí)例被創(chuàng)建的方法,所以它是為新對(duì)象屬性填充實(shí)例默認(rèn)值以及其他任務(wù)的明智之選。 舉個(gè)例子可能更清楚。

簡(jiǎn)單示例 - 向 ndarray 添加額外屬性

  1. import numpy as np
  2. class InfoArray(np.ndarray):
  3. def __new__(subtype, shape, dtype=float, buffer=None, offset=0,
  4. strides=None, order=None, info=None):
  5. # Create the ndarray instance of our type, given the usual
  6. # ndarray input arguments. This will call the standard
  7. # ndarray constructor, but return an object of our type.
  8. # It also triggers a call to InfoArray.__array_finalize__
  9. obj = super().__new__(subtype, shape, dtype,
  10. buffer, offset, strides, order)
  11. # set the new 'info' attribute to the value passed
  12. obj.info = info
  13. # Finally, we must return the newly created object:
  14. return obj
  15. def __array_finalize__(self, obj):
  16. # ``self`` is a new object resulting from
  17. # ndarray.__new__(InfoArray, ...), therefore it only has
  18. # attributes that the ndarray.__new__ constructor gave it -
  19. # i.e. those of a standard ndarray.
  20. #
  21. # We could have got to the ndarray.__new__ call in 3 ways:
  22. # From an explicit constructor - e.g. InfoArray():
  23. # obj is None
  24. # (we're in the middle of the InfoArray.__new__
  25. # constructor, and self.info will be set when we return to
  26. # InfoArray.__new__)
  27. if obj is None: return
  28. # From view casting - e.g arr.view(InfoArray):
  29. # obj is arr
  30. # (type(obj) can be InfoArray)
  31. # From new-from-template - e.g infoarr[:3]
  32. # type(obj) is InfoArray
  33. #
  34. # Note that it is here, rather than in the __new__ method,
  35. # that we set the default value for 'info', because this
  36. # method sees all creation of default objects - with the
  37. # InfoArray.__new__ constructor, but also with
  38. # arr.view(InfoArray).
  39. self.info = getattr(obj, 'info', None)
  40. # We do not need to return anything

使用對(duì)象如下所示:

  1. >>> obj = InfoArray(shape=(3,)) # explicit constructor
  2. >>> type(obj)
  3. <class 'InfoArray'>
  4. >>> obj.info is None
  5. True
  6. >>> obj = InfoArray(shape=(3,), info='information')
  7. >>> obj.info
  8. 'information'
  9. >>> v = obj[1:] # new-from-template - here - slicing
  10. >>> type(v)
  11. <class 'InfoArray'>
  12. >>> v.info
  13. 'information'
  14. >>> arr = np.arange(10)
  15. >>> cast_arr = arr.view(InfoArray) # view casting
  16. >>> type(cast_arr)
  17. <class 'InfoArray'>
  18. >>> cast_arr.info is None
  19. True

這個(gè)類不是很有用,因?yàn)樗哂信c裸 ndarray 對(duì)象相同的構(gòu)造函數(shù),包括傳入緩沖區(qū)和形狀等。我們可能更希望構(gòu)造函數(shù)能夠從通常的 numpy 調(diào)用中獲取一個(gè)已經(jīng)形成的 ndarraynp.array并返回一個(gè)對(duì)象。

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

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

  1. import numpy as np
  2. class RealisticInfoArray(np.ndarray):
  3. def __new__(cls, input_array, info=None):
  4. # Input array is an already formed ndarray instance
  5. # We first cast to be our class type
  6. obj = np.asarray(input_array).view(cls)
  7. # add the new attribute to the created instance
  8. obj.info = info
  9. # Finally, we must return the newly created object:
  10. return obj
  11. def __array_finalize__(self, obj):
  12. # see InfoArray.__array_finalize__ for comments
  13. if obj is None: return
  14. self.info = getattr(obj, 'info', None)

所以:

  1. >>> arr = np.arange(5)
  2. >>> obj = RealisticInfoArray(arr, info='information')
  3. >>> type(obj)
  4. <class 'RealisticInfoArray'>
  5. >>> obj.info
  6. 'information'
  7. >>> v = obj[1:]
  8. >>> type(v)
  9. <class 'RealisticInfoArray'>
  10. >>> v.info
  11. 'information'

3 __array_ufunc__對(duì)于 ufunc?

1.13 版中的新功能。

子類可以通過(guò)覆蓋默認(rèn)ndarray.__array_ufunc__方法來(lái)覆蓋在其上執(zhí)行 numpy ufuncs 時(shí)發(fā)生的情況。執(zhí)行此方法而不是ufunc 并且應(yīng)該返回操作的結(jié)果,或者NotImplemented如果請(qǐng)求的操作沒(méi)有實(shí)現(xiàn)。

的簽名__array_ufunc__是:

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

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

  1. import numpy as np
  2. class A(np.ndarray):
  3. def __array_ufunc__(self, ufunc, method, *inputs, out=None, **kwargs):
  4. args = []
  5. in_no = []
  6. for i, input_ in enumerate(inputs):
  7. if isinstance(input_, A):
  8. in_no.append(i)
  9. args.append(input_.view(np.ndarray))
  10. else:
  11. args.append(input_)
  12. outputs = out
  13. out_no = []
  14. if outputs:
  15. out_args = []
  16. for j, output in enumerate(outputs):
  17. if isinstance(output, A):
  18. out_no.append(j)
  19. out_args.append(output.view(np.ndarray))
  20. else:
  21. out_args.append(output)
  22. kwargs['out'] = tuple(out_args)
  23. else:
  24. outputs = (None,) * ufunc.nout
  25. info = {}
  26. if in_no:
  27. info['inputs'] = in_no
  28. if out_no:
  29. info['outputs'] = out_no
  30. results = super().__array_ufunc__(ufunc, method, *args, **kwargs)
  31. if results is NotImplemented:
  32. return NotImplemented
  33. if method == 'at':
  34. if isinstance(inputs[0], A):
  35. inputs[0].info = info
  36. return
  37. if ufunc.nout == 1:
  38. results = (results,)
  39. results = tuple((np.asarray(result).view(A)
  40. if output is None else output)
  41. for result, output in zip(results, outputs))
  42. if results and isinstance(results[0], A):
  43. results[0].info = info
  44. return results[0] if len(results) == 1 else results

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

  1. >>> a = np.arange(5.).view(A)
  2. >>> b = np.sin(a)
  3. >>> b.info
  4. {'inputs': [0]}
  5. >>> b = np.sin(np.arange(5.), out=(a,))
  6. >>> b.info
  7. {'outputs': [0]}
  8. >>> a = np.arange(5.).view(A)
  9. >>> b = np.ones(1).view(A)
  10. >>> c = a + b
  11. >>> c.info
  12. {'inputs': [0, 1]}
  13. >>> a += b
  14. >>> a.info
  15. {'inputs': [0, 1], 'outputs': [0]}

請(qǐng)注意,另一種方法是使用而不是調(diào)用。對(duì)于此示例,結(jié)果將是相同的,但如果另一個(gè)操作數(shù)也定義了 ,則會(huì)有所不同。例如,讓我們假設(shè)我們?cè)u(píng)估?,其中是另一個(gè)具有覆蓋的類的實(shí)例。如果您在示例中使用as ,?會(huì)注意到它具有覆蓋,這意味著它無(wú)法評(píng)估結(jié)果本身。因此,它將返回NotImplemented,我們的 class也將返回?。然后,控制將傳遞給,它要么知道如何處理我們并產(chǎn)生結(jié)果,要么不知道并返回NotImplemented,從而引發(fā).getattr(ufunc,?methods)(*inputs,?**kwargs)``super``__array_ufunc__``np.add(a,?b)``b``B``super``ndarray.__array_ufunc__``b``A``b``TypeError

相反,如果我們用 替換我們的super電話,我們就有效地做到了。同樣,?將被調(diào)用,但現(xiàn)在它將 an視為另一個(gè)參數(shù)。很可能,它會(huì)知道如何處理這個(gè)問(wèn)題,并將該類的一個(gè)新實(shí)例返回給我們。我們的示例類沒(méi)有設(shè)置來(lái)處理這個(gè)問(wèn)題,但如果,例如,要使用?重新實(shí)現(xiàn),它可能是最好的方法。getattr(ufunc,?method)``np.add(a.view(np.ndarray),?b)``B.__array_ufunc__``ndarray``B``MaskedArray``__array_ufunc__

最后要注意的是:如果super路由適合給定的類,使用它的好處是它有助于構(gòu)建類層次結(jié)構(gòu)。例如,假設(shè)我們的另一個(gè)類在其?實(shí)現(xiàn)中B也使用了,并且我們創(chuàng)建了一個(gè)依賴于兩者的類,即(為了簡(jiǎn)單起見(jiàn),沒(méi)有另一個(gè)?覆蓋)。然后,實(shí)例上的任何 ufunc將傳遞給,調(diào)用將轉(zhuǎn)到?,調(diào)用將轉(zhuǎn)到?,從而允許并進(jìn)行協(xié)作。super``__array_ufunc__``C``class?C(A,?B)``__array_ufunc__``C``A.__array_ufunc__``super``A``B.__array_ufunc__``super``B``ndarray.__array_ufunc__``A``B

__array_wrap__對(duì)于 ufuncs 和其他函數(shù)

在 numpy 1.13 之前,ufunc 的行為只能使用__array_wrap__和進(jìn)行調(diào)整?__array_prepare__。這兩個(gè)允許更改 ufunc 的輸出類型,但與 相比?__array_ufunc__,不允許對(duì)輸入進(jìn)行任何更改。希望最終棄用這些,但__array_wrap__也被其他 numpy 函數(shù)和方法使用,例如squeeze,因此目前仍需要完整功能。

從概念上講,__array_wrap__“包裝動(dòng)作”是指允許子類設(shè)置返回值的類型并更新屬性和元數(shù)據(jù)。讓我們用一個(gè)例子來(lái)展示它是如何工作的。首先我們回到更簡(jiǎn)單的示例子類,但使用不同的名稱和一些打印語(yǔ)句:

  1. import numpy as np
  2. class MySubClass(np.ndarray):
  3. def __new__(cls, input_array, info=None):
  4. obj = np.asarray(input_array).view(cls)
  5. obj.info = info
  6. return obj
  7. def __array_finalize__(self, obj):
  8. print('In __array_finalize__:')
  9. print(' self is %s' % repr(self))
  10. print(' obj is %s' % repr(obj))
  11. if obj is None: return
  12. self.info = getattr(obj, 'info', None)
  13. def __array_wrap__(self, out_arr, context=None):
  14. print('In __array_wrap__:')
  15. print(' self is %s' % repr(self))
  16. print(' arr is %s' % repr(out_arr))
  17. # then just call the parent
  18. return super().__array_wrap__(self, out_arr, context)

我們?cè)谛聰?shù)組的一個(gè)實(shí)例上運(yùn)行一個(gè) ufunc:

  1. >>> obj = MySubClass(np.arange(5), info='spam')
  2. In __array_finalize__:
  3. self is MySubClass([0, 1, 2, 3, 4])
  4. obj is array([0, 1, 2, 3, 4])
  5. >>> arr2 = np.arange(5)+1
  6. >>> ret = np.add(arr2, obj)
  7. In __array_wrap__:
  8. self is MySubClass([0, 1, 2, 3, 4])
  9. arr is array([1, 3, 5, 7, 9])
  10. In __array_finalize__:
  11. self is MySubClass([1, 3, 5, 7, 9])
  12. obj is MySubClass([0, 1, 2, 3, 4])
  13. >>> ret
  14. MySubClass([1, 3, 5, 7, 9])
  15. >>> ret.info
  16. 'spam'

請(qǐng)注意, ufunc (?np.add) 調(diào)用了__array_wrap__帶有參數(shù)selfas的方法obj,以及out_arr作為加法的 (ndarray) 結(jié)果。反過(guò)來(lái),默認(rèn)__array_wrap__?(?ndarray.__array_wrap__) 已將結(jié)果強(qiáng)制轉(zhuǎn)換為 class?MySubClass,并調(diào)用__array_finalize__- 因此復(fù)制info?屬性。這一切都發(fā)生在 C 級(jí)。

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

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

因此,通過(guò)__array_wrap__為我們的子類定義一個(gè)特定的方法,我們可以調(diào)整 ufuncs 的輸出。該__array_wrap__方法需要self,然后是一個(gè)參數(shù)——它是 ufunc 的結(jié)果——和一個(gè)可選的參數(shù)上下文。此參數(shù)由 ufuncs 作為 3 元素元組返回:(ufunc 的名稱,ufunc 的參數(shù),ufunc 的域),但不由其他 numpy 函數(shù)設(shè)置。雖然,如上所見(jiàn),有可能以其他方式執(zhí)行,但__array_wrap__應(yīng)返回其包含類的實(shí)例。有關(guān)實(shí)現(xiàn),請(qǐng)參閱掩碼數(shù)組子類。

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

額外的問(wèn)題 - 自定義__del__方法和 ndarray.base?

ndarray 解決的問(wèn)題之一是跟蹤 ndarray 及其視圖的內(nèi)存所有權(quán)。考慮我們創(chuàng)建了一個(gè) ndarrayarr并使用.?這兩個(gè)對(duì)象正在查看相同的內(nèi)存。NumPy 使用以下屬性跟蹤特定數(shù)組或視圖的數(shù)據(jù)來(lái)自何處?:v?=?arr[1:]``base

  1. >>> # A normal ndarray, that owns its own data
  2. >>> arr = np.zeros((4,))
  3. >>> # In this case, base is None
  4. >>> arr.base is None
  5. True
  6. >>> # We take a view
  7. >>> v1 = arr[1:]
  8. >>> # base now points to the array that it derived from
  9. >>> v1.base is arr
  10. True
  11. >>> # Take a view of a view
  12. >>> v2 = v1[1:]
  13. >>> # base points to the original array that it was derived from
  14. >>> v2.base is arr
  15. True

一般來(lái)說(shuō),如果數(shù)組擁有自己的內(nèi)存,就arr在這種情況下,那么arr.base將是 None - 有一些例外 - 有關(guān)更多詳細(xì)信息,請(qǐng)參閱 numpy book。

base屬性有助于判斷我們是否擁有視圖或原始數(shù)組。如果我們需要知道在刪除子類數(shù)組時(shí)是否進(jìn)行一些特定的清理,這反過(guò)來(lái)會(huì)很有用。例如,如果原始數(shù)組被刪除,我們可能只想做清理,而不是視圖。對(duì)于如何能工作的例子,看看在memmap上課?numpy.core。

子類化和下游兼容性

當(dāng)子類化ndarray或創(chuàng)建模仿ndarray?接口的鴨子類型時(shí),您有責(zé)任決定您的 API 與 numpy 的 API 的對(duì)齊程度。為了方便起見(jiàn),具有相應(yīng)的許多numpy的功能?ndarray的方法(例如,sum,mean,take,reshape)通過(guò)檢查第一個(gè)參數(shù)的函數(shù)具有相同的名稱的方法工作。如果存在,則調(diào)用該方法,而不是將參數(shù)強(qiáng)制轉(zhuǎn)換為 numpy 數(shù)組。

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

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

這是完全相同的方法簽名np.sum,所以現(xiàn)在如果用戶調(diào)用?np.sum這個(gè)對(duì)象,numpy將調(diào)用對(duì)象自己的sum方法并在簽名中傳遞上面枚舉的這些參數(shù),并且不會(huì)引發(fā)錯(cuò)誤,因?yàn)楹灻耆嫒荼舜恕?/p>

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

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

此對(duì)象不再與 兼容,np.sum因?yàn)槿绻{(diào)用np.sum,它將傳入意外的參數(shù)outkeepdims,從而引發(fā) TypeError 。

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

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

這個(gè)對(duì)象現(xiàn)在np.sum再次兼容,因?yàn)槿魏螣o(wú)關(guān)的參數(shù)(即不是axis或 的關(guān)鍵字dtype)將隱藏在?**unused_kwargs參數(shù)中。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)