標準陣列子類別#

注意

子類別化 numpy.ndarray 是可能的,但如果您的目標是建立具有修改後 行為的陣列,如同 dask 陣列用於分散式計算和 cupy 陣列用於基於 GPU 的計算那樣,則不建議子類別化。相反地,建議使用 numpy 的 分派機制

如果需要,可以從 ndarray 繼承(在 Python 或 C 中)。因此,它可以構成許多有用類別的基礎。通常,要子類別化陣列物件,還是僅將核心陣列組件用作新類別的內部部分是一個難以決定的問題,並且可能僅僅是選擇的問題。NumPy 具有多種工具,可簡化您的新物件與其他陣列物件互動的方式,因此最終選擇可能並不重要。簡化問題的一種方法是問自己,您感興趣的物件是否可以替換為單一陣列,還是真的需要兩個或更多陣列作為其核心。

請注意,asarray 始終傳回基底類別 ndarray。如果您確信您對陣列物件的使用可以處理 ndarray 的任何子類別,則可以使用 asanyarray 以允許子類別更乾淨地在您的子程式中傳播。原則上,子類別可以重新定義陣列的任何方面,因此,在嚴格的準則下,asanyarray 很少有用。但是,大多數陣列物件的子類別不會重新定義陣列物件的某些方面,例如緩衝區介面或陣列的屬性。然而,一個重要的例子是,為什麼您的子程式可能無法處理陣列的任意子類別,那是因為矩陣重新定義了“*”運算符,使其成為矩陣乘法,而不是逐元素乘法。

特殊屬性和方法#

另請參閱

子類別化 ndarray

NumPy 提供了幾個類別可以自訂的掛鉤

class.__array_ufunc__(ufunc, method, *inputs, **kwargs)#

任何類別,無論是否為 ndarray 子類別,都可以定義此方法或將其設定為 None,以覆寫 NumPy ufunc 的行為。這與 Python 的 __mul__ 和其他二元運算常式非常相似。

  • ufunc 是被呼叫的 ufunc 物件。

  • method 是一個字串,指示呼叫了哪個 Ufunc 方法("__call__""reduce""reduceat""accumulate""outer""inner" 之一)。

  • inputsufunc 的輸入引數的元組。

  • kwargs 是一個字典,包含 ufunc 的可選輸入引數。如果給定,則任何 out 引數(位置和關鍵字)都作為 tuple 傳遞到 kwargs 中。有關詳細資訊,請參閱 通用函數 (ufunc) 中的討論。

如果方法實作了請求的操作,則應傳回操作的結果,否則如果未實作請求的操作,則傳回 NotImplemented

如果輸入、輸出或 where 引數之一具有 __array_ufunc__ 方法,則會取代 ufunc 執行它。如果多個引數實作了 __array_ufunc__,則它們會依以下順序嘗試:子類別優先於超類別,輸入優先於輸出,輸出優先於 where,否則從左到右。第一個傳回非 NotImplemented 值的常式決定結果。如果所有 __array_ufunc__ 操作都傳回 NotImplemented,則會引發 TypeError

注意

我們打算將 numpy 函數重新實作為(廣義)Ufunc,在這種情況下,它們將可能被 __array_ufunc__ 方法覆寫。主要的候選者是 matmul,它目前不是 Ufunc,但可以相對容易地重寫為(一組)廣義 Ufunc。諸如 medianaminargsort 等函數也可能發生同樣的情況。

與 python 中的其他一些特殊方法(例如 __hash____iter__)一樣,可以透過設定 __array_ufunc__ = None 來指示您的類別支援 ufunc。當在設定了 __array_ufunc__ = None 的物件上呼叫 Ufunc 時,Ufunc 始終會引發 TypeError

__array_ufunc__ 的存在也會影響 ndarray 如何處理二元運算,例如 arr + objarr < obj,當 arrndarrayobj 是自訂類別的實例時。有兩種可能性。如果 obj.__array_ufunc__ 存在且非 None,則 ndarray.__add__ 和相關函數將委派給 ufunc 機制,這表示 arr + obj 變成 np.add(arr, obj),然後 add 調用 obj.__array_ufunc__。如果您想定義一個行為類似陣列的物件,這會很有用。

或者,如果 obj.__array_ufunc__ 設定為 None,則作為特殊情況,諸如 ndarray.__add__ 之類的特殊方法會注意到這一點,並無條件引發 TypeError。如果您想建立透過二元運算與陣列互動,但本身不是陣列的物件,這會很有用。例如,單位處理系統可能有一個物件 m 代表「公尺」單位,並且想要支援語法 arr * m 來表示陣列具有「公尺」單位,但不希望透過 ufunc 或其他方式與陣列互動。這可以透過設定 __array_ufunc__ = None 並定義 __mul____rmul__ 方法來完成。(請注意,這表示編寫一個始終傳回 NotImplemented__array_ufunc__ 與設定 __array_ufunc__ = None 並不完全相同:在前一種情況下,arr + obj 將引發 TypeError,而在後一種情況下,可以定義 __radd__ 方法來防止這種情況。)

上述情況不適用於就地運算符,ndarray 永遠不會為就地運算符傳回 NotImplemented。因此,arr += obj 始終會導致 TypeError。這是因為對於陣列,就地運算無法以簡單的反向運算通用地替換。(例如,預設情況下,arr += obj 會轉換為 arr = arr + obj,即 arr 會被替換,這與就地陣列運算的預期相反。)

注意

如果您定義 __array_ufunc__

  • 如果您不是 ndarray 的子類別,我們建議您的類別定義特殊方法,例如 __add____lt__,這些方法會像 ndarray 一樣委派給 ufunc。執行此操作的簡單方法是從 NDArrayOperatorsMixin 子類別化。

  • 如果您子類別化 ndarray,我們建議您將所有覆寫邏輯放在 __array_ufunc__ 中,而不是也覆寫特殊方法。這可確保類別階層僅在一個地方確定,而不是由 ufunc 機制和二元運算規則(優先考慮子類別的特殊方法;強制執行單一位置階層的替代方法,即將 __array_ufunc__ 設定為 None,似乎非常出乎意料且令人困惑,因為這樣一來,子類別將完全無法與 ufunc 一起使用)。

  • ndarray 定義了自己的 __array_ufunc__,如果沒有引數具有覆寫,則評估 ufunc,否則傳回 NotImplemented。這對於子類別可能很有用,對於這些子類別,__array_ufunc__ 會將其自身類別的任何實例轉換為 ndarray:然後可以使用 super().__array_ufunc__(*inputs, **kwargs) 將這些傳遞給其超類別,並在可能的向後轉換後最終傳回結果。這種做法的優點是,它可以確保可以擁有擴展行為的子類別階層。請參閱 子類別化 ndarray 以取得詳細資訊。

class.__array_function__(func, types, args, kwargs)#
  • func 是 NumPy 公開 API 公開的任意可呼叫物件,以 func(*args, **kwargs) 的形式呼叫。

  • types 是原始 NumPy 函數呼叫中實作 __array_function__ 的唯一引數型別的集合 collections.abc.Collection

  • 元組 args 和字典 kwargs 直接從原始呼叫傳遞。

為了方便 __array_function__ 實作人員,types 提供了具有 '__array_function__' 屬性的所有引數型別。這允許實作人員快速識別他們應將控制權轉移給其他引數上的 __array_function__ 實作的情況。實作不應依賴 types 的迭代順序。

__array_function__ 的大多數實作將從兩個檢查開始

  1. 給定的函數是我們知道如何過載的函數嗎?

  2. 所有引數都是我們知道如何處理的型別嗎?

如果這些條件成立,則 __array_function__ 應傳回從呼叫其 func(*args, **kwargs) 實作的結果。否則,它應傳回 sentinel 值 NotImplemented,表示這些型別未實作該函數。

對於 __array_function__ 的傳回值沒有一般要求,儘管大多數合理的實作可能應傳回與函數引數之一具有相同型別的陣列。

定義自訂裝飾器(下面的 implements)以註冊 __array_function__ 實作也可能很方便。

HANDLED_FUNCTIONS = {}

class MyArray:
    def __array_function__(self, func, types, args, kwargs):
        if func not in HANDLED_FUNCTIONS:
            return NotImplemented
        # Note: this allows subclasses that don't override
        # __array_function__ to handle MyArray objects
        if not all(issubclass(t, MyArray) for t in types):
            return NotImplemented
        return HANDLED_FUNCTIONS[func](*args, **kwargs)

def implements(numpy_function):
    """Register an __array_function__ implementation for MyArray objects."""
    def decorator(func):
        HANDLED_FUNCTIONS[numpy_function] = func
        return func
    return decorator

@implements(np.concatenate)
def concatenate(arrays, axis=0, out=None):
    ...  # implementation of concatenate for MyArray objects

@implements(np.broadcast_to)
def broadcast_to(array, shape):
    ...  # implementation of broadcast_to for MyArray objects

請注意,__array_function__ 實作不需要包含所有對應的 NumPy 函數的可選引數(例如,上面的 broadcast_to 省略了不相關的 subok 引數)。僅當在 NumPy 函數呼叫中明確使用可選引數時,才會將它們傳遞到 __array_function__ 中。

就像內建特殊方法(例如 __add__)的情況一樣,正確編寫的 __array_function__ 方法在遇到未知型別時應始終傳回 NotImplemented。否則,如果操作也包含您的物件之一,則將無法從另一個物件正確覆寫 NumPy 函數。

在大多數情況下,使用 __array_function__ 進行分派的規則與 __array_ufunc__ 的規則相符。特別是

  • NumPy 將從所有指定的輸入收集 __array_function__ 的實作,並按以下順序呼叫它們:子類別優先於超類別,否則從左到右。請注意,在某些涉及子類別的邊緣情況下,這與 Python 的 當前行為略有不同。

  • __array_function__ 的實作透過傳回除 NotImplemented 之外的任何值來指示它們可以處理該操作。

  • 如果所有 __array_function__ 方法都傳回 NotImplemented,則 NumPy 將引發 TypeError

如果不存在 __array_function__ 方法,NumPy 將預設為呼叫其自己的實作,旨在用於 NumPy 陣列。例如,當所有類似陣列的引數都是 Python 數字或列表時,就會出現這種情況。(NumPy 陣列確實有一個 __array_function__ 方法,如下所示,但如果任何 NumPy 陣列子類別以外的引數實作了 __array_function__,它始終會傳回 NotImplemented。)

__array_ufunc__ 的目前行為的一個偏差是,NumPy 將僅對每個唯一型別的第一個引數呼叫 __array_function__。這與 Python 的 呼叫反射方法的規則相符,這可確保即使有大量過載引數,檢查過載也具有可接受的效能。

class.__array_finalize__(obj)#

每當系統從 obj 內部配置新陣列時,都會呼叫此方法,其中 objndarray 的子類別(子型別)。它可用於在建構後變更 self 的屬性(例如,確保 2 維矩陣),或從「父物件」更新元資訊。子類別繼承此方法的預設實作,該實作不執行任何操作。

class.__array_wrap__(array, context=None, return_scalar=False)#

在每個 ufunc 的結尾,會在具有最高陣列優先順序的輸入物件或指定的輸出物件上呼叫此方法。ufunc 計算的陣列會傳遞進來,傳回的任何內容都會傳遞給使用者。子類別繼承此方法的預設實作,該實作將陣列轉換為物件類別的新實例。子類別可以選擇使用此方法將輸出陣列轉換為子類別的實例,並在將陣列傳回給使用者之前更新元資料。

NumPy 也可能在沒有來自非 ufunc 的上下文的情況下呼叫此函數,以允許保留子類別資訊。

在 2.0 版本中變更: return_scalar 現在作為 False(通常)或 True 傳遞,表示 NumPy 將傳回純量。子類別可以忽略該值,或傳回 array[()] 以更像 NumPy 的行為。

注意

希望最終棄用此方法,轉而支持 ufunc 的 __array_ufunc__(以及少數其他函數(例如 numpy.squeeze)的 __array_function__)。

class.__array_priority__#

此屬性的值用於確定在傳回物件的 Python 型別有多種可能性的情況下,要傳回哪種類型的物件。子類別繼承此屬性的預設值 0.0。

注意

對於 ufunc,希望最終棄用此方法,轉而支持 __array_ufunc__

class.__array__(dtype=None, copy=None)#

如果在物件上定義,則應傳回 ndarray。如果實作此介面的物件傳遞給 np.array() 等陣列強制轉型函數,則會呼叫此方法。 __array__ 的第三方實作必須採用 dtypecopy 關鍵字引數,因為忽略它們可能會破壞第三方程式碼或 NumPy 本身。

  • dtype 是傳回陣列的資料型別。

  • copy 是一個可選的布林值,指示是否應傳回副本。對於 True,應始終建立副本;對於 None,僅在需要時(例如,由於傳遞的 dtype 值);對於 False,永遠不應建立副本(如果仍然需要副本,則應引發適當的例外)。

請參閱 與 NumPy 的互通性 以瞭解協定階層,其中 __array__ 是最舊且最不理想的。

注意

如果具有 __array__ 方法的類別(ndarray 子類別與否)用作 ufunc 的輸出物件,則結果不會寫入到 __array__ 傳回的物件中。這種做法將傳回 TypeError

矩陣物件#

注意

強烈建議不要使用 matrix 子類別。如下所述,這會使得編寫能一致處理矩陣和常規陣列的函式變得非常困難。目前,它們主要用於與 scipy.sparse 互動。我們希望能為此用途提供替代方案,並最終移除 matrix 子類別。

matrix 物件繼承自 ndarray,因此,它們擁有與 ndarray 相同的屬性和方法。然而,matrix 物件有六個重要的差異,當您使用矩陣但期望它們像陣列一樣運作時,可能會導致意想不到的結果

  1. 矩陣物件可以使用字串表示法建立,以允許 Matlab 風格的語法,其中空格分隔列,而分號(‘;’)分隔行。

  2. 矩陣物件始終是二維的。這具有深遠的影響,因為 m.ravel() 仍然是二維的(第一個維度為 1),並且項目選擇會傳回二維物件,因此序列行為與陣列從根本上不同。

  3. 矩陣物件會覆寫乘法運算,使其成為矩陣乘法。請務必理解這一點,對於您可能希望接收矩陣的函式而言尤其重要。特別是考慮到當 m 是矩陣時,asanyarray(m) 會傳回矩陣。

  4. 矩陣物件會覆寫冪運算,使其成為矩陣的冪次方。對於在函式內部使用冪運算的情況,該函式使用 asanyarray(…) 來取得陣列物件,這項事實也適用相同的警告。

  5. 矩陣物件的預設 __array_priority__ 為 10.0,因此與 ndarray 的混合運算始終產生矩陣。

  6. 矩陣具有特殊的屬性,可簡化計算。這些屬性是

    matrix.T

    傳回矩陣的轉置。

    matrix.H

    傳回 self 的(複數)共軛轉置。

    matrix.I

    傳回可逆 self 的(乘法)逆矩陣。

    matrix.A

    selfndarray 物件傳回。

警告

矩陣物件會覆寫乘法運算符號 ‘*’ 和冪運算符號 ‘**’,使其分別成為矩陣乘法和矩陣冪次方。如果您的子程序可以接受子類別,並且您不轉換為基礎類別陣列,那麼您必須使用 ufuncs multiply 和 power,以確保您對所有輸入執行正確的運算。

matrix 類別是 ndarray 的 Python 子類別,可以用作如何建構您自己的 ndarray 子類別的參考。矩陣可以從其他矩陣、字串以及任何可以轉換為 ndarray 的事物建立。「mat」是 NumPy 中「matrix」的別名。

matrix(data[, dtype, copy])

從類陣列物件或資料字串傳回矩陣。

asmatrix(data[, dtype])

將輸入解譯為矩陣。

bmat(obj[, ldict, gdict])

從字串、巢狀序列或陣列建構矩陣物件。

範例 1:從字串建立矩陣

>>> import numpy as np
>>> a = np.asmatrix('1 2 3; 4 5 3')
>>> print((a*a.T).I)
  [[ 0.29239766 -0.13450292]
   [-0.13450292  0.08187135]]

範例 2:從巢狀序列建立矩陣

>>> import numpy as np
>>> np.asmatrix([[1,5,10],[1.0,3,4j]])
matrix([[  1.+0.j,   5.+0.j,  10.+0.j],
        [  1.+0.j,   3.+0.j,   0.+4.j]])

範例 3:從陣列建立矩陣

>>> import numpy as np
>>> np.asmatrix(np.random.rand(3,3)).T
matrix([[4.17022005e-01, 3.02332573e-01, 1.86260211e-01],
        [7.20324493e-01, 1.46755891e-01, 3.45560727e-01],
        [1.14374817e-04, 9.23385948e-02, 3.96767474e-01]])

記憶體對應檔案陣列#

記憶體對應檔案適用於讀取和/或修改具有規則佈局的大型檔案的小區段,而無需將整個檔案讀取到記憶體中。ndarray 的一個簡單子類別使用記憶體對應檔案作為陣列的資料緩衝區。對於小檔案,將整個檔案讀取到記憶體中的額外負擔通常並不顯著,但是對於大型檔案,使用記憶體對應可以節省大量資源。

記憶體對應檔案陣列有一個額外的方法(除了它們從 ndarray 繼承的方法之外):.flush(),使用者必須手動呼叫此方法,以確保對陣列的任何變更實際寫入磁碟。

memmap(filename[, dtype, mode, offset, ...])

建立磁碟上二進位檔案中儲存的陣列的記憶體對應。

memmap.flush()

將陣列中的任何變更寫入磁碟上的檔案。

範例

>>> import numpy as np
>>> a = np.memmap('newfile.dat', dtype=float, mode='w+', shape=1000)
>>> a[10] = 10.0
>>> a[30] = 30.0
>>> del a
>>> b = np.fromfile('newfile.dat', dtype=float)
>>> print(b[10], b[30])
10.0 30.0
>>> a = np.memmap('newfile.dat', dtype=float)
>>> print(a[10], a[30])
10.0 30.0

字元陣列 (numpy.char)#

注意

chararray 類別的存在是為了向後相容 Numarray,不建議用於新的開發。從 numpy 1.4 開始,如果需要字串陣列,建議使用 dtype object_bytes_str_ 的陣列,並使用 numpy.char 模組中的自由函式進行快速向量化字串運算。

這些是 str_ 類型或 bytes_ 類型的增強型陣列。這些陣列繼承自 ndarray,但特別定義了在(廣播)逐元素基礎上的運算符號 +*%。這些運算在標準 ndarray 的字元類型上不可用。此外,chararray 具有所有標準 str(和 bytes)方法,在逐元素基礎上執行它們。建立 chararray 也許最簡單的方法是使用 self.view(chararray),其中 self 是 str 或 unicode 資料類型的 ndarray。但是,也可以使用 chararray 建構函式或透過 numpy.char.array 函式來建立 chararray

char.chararray(shape[, itemsize, unicode, ...])

提供字串和 unicode 值陣列的便捷檢視。

char.array(obj[, itemsize, copy, unicode, order])

建立 chararray

與標準 str 資料類型的 ndarray 的另一個區別是,chararray 繼承了 Numarray 引入的功能,即陣列中任何元素結尾的空白字元在項目檢索和比較運算中將被忽略。

記錄陣列#

NumPy 提供了 recarray 類別,該類別允許將結構化陣列的欄位作為屬性存取,以及對應的純量資料類型物件 record

recarray(shape[, dtype, buf, offset, ...])

建構一個允許使用屬性進行欄位存取的 ndarray。

record

一個資料類型純量,允許將欄位存取作為屬性查找。

注意

pandas DataFrame 比記錄陣列更強大。如果可能,請改用 pandas DataFrame。

遮罩陣列 (numpy.ma)#

另請參閱

遮罩陣列

標準容器類別#

為了向後相容性並作為標準「容器」類別,Numeric 中的 UserArray 已被引入 NumPy 並命名為 numpy.lib.user_array.container。容器類別是一個 Python 類別,其 self.array 屬性是一個 ndarray。多重繼承對於 numpy.lib.user_array.container 可能比 ndarray 本身更容易,因此預設包含它。此處未對其進行詳細說明,僅提及它的存在,因為如果可以,我們鼓勵您直接使用 ndarray 類別。

numpy.lib.user_array.container(data[, ...])

用於簡單多重繼承的標準容器類別。

陣列迭代器#

迭代器是陣列處理的強大概念。本質上,迭代器實作了廣義的 for 迴圈。如果 myiter 是一個迭代器物件,那麼 Python 程式碼

for val in myiter:
    ...
    some code involving val
    ...

會重複呼叫 val = next(myiter),直到迭代器引發 StopIteration 為止。有幾種迭代陣列的方式可能很有用:預設迭代、扁平迭代和 \(N\) 維度列舉。

預設迭代#

ndarray 物件的預設迭代器是序列類型的預設 Python 迭代器。因此,當陣列物件本身用作迭代器時。預設行為等效於

for i in range(arr.shape[0]):
    val = arr[i]

此預設迭代器從陣列中選取維度為 \(N-1\) 的子陣列。這對於定義遞迴演算法可能很有用。要迴圈遍歷整個陣列,需要 \(N\) 個 for 迴圈。

>>> import numpy as np
>>> a = np.arange(24).reshape(3,2,4) + 10
>>> for val in a:
...     print('item:', val)
item: [[10 11 12 13]
[14 15 16 17]]
item: [[18 19 20 21]
[22 23 24 25]]
item: [[26 27 28 29]
[30 31 32 33]]

扁平迭代#

ndarray.flat

陣列上的 1-D 迭代器。

如先前所述,ndarray 物件的 flat 屬性傳回一個迭代器,該迭代器將以 C 風格的連續順序循環遍歷整個陣列。

>>> import numpy as np
>>> a = np.arange(24).reshape(3,2,4) + 10
>>> for i, val in enumerate(a.flat):
...     if i%5 == 0: print(i, val)
0 10
5 15
10 20
15 25
20 30

在這裡,我使用了內建的 enumerate 迭代器來傳回迭代器索引以及值。

N 維度列舉#

ndenumerate(arr)

多維度索引迭代器。

有時,在迭代時取得 N 維度索引可能很有用。ndenumerate 迭代器可以實現這一點。

>>> import numpy as np
>>> for i, val in np.ndenumerate(a):
...     if sum(i)%5 == 0: print(i, val)
(0, 0, 0) 10
(1, 1, 3) 25
(2, 0, 3) 29
(2, 1, 2) 32

用於廣播的迭代器#

broadcast

產生一個模仿廣播的物件。

廣播的一般概念也可以從 Python 中使用 broadcast 迭代器取得。此物件將 \(N\) 個物件作為輸入,並傳回一個迭代器,該迭代器傳回元組,在廣播結果中提供每個輸入序列元素。

>>> import numpy as np
>>> for val in np.broadcast([[1, 0], [2, 3]], [0, 1]):
...     print(val)
(np.int64(1), np.int64(0))
(np.int64(0), np.int64(1))
(np.int64(2), np.int64(0))
(np.int64(3), np.int64(1))