緩衝區協議¶
Python 中可用的某些物件封裝了對底層記憶體陣列或緩衝區的訪問。這些物件包括內建的 bytes
和 bytearray
,以及一些擴充套件型別,例如 array.array
。第三方庫可能會為特殊目的定義自己的型別,例如影像處理或數值分析。
雖然這些型別中的每一種都有自己的語義,但它們都具有由一個可能很大的記憶體緩衝區支援的共同特徵。因此,在某些情況下,希望直接訪問該緩衝區,而無需中間複製。
Python 以 緩衝區協議 的形式在 C 級別提供這種功能。此協議有兩方面
在生產者方面,型別可以匯出“緩衝區介面”,從而使該型別的物件可以公開有關其底層緩衝區的資訊。此介面在 緩衝區物件結構 部分中進行了描述;
在使用者方面,有多種方法可以獲取指向物件原始底層資料的指標(例如,方法引數)。
諸如 bytes
和 bytearray
之類的簡單物件以面向位元組的形式公開其底層緩衝區。其他形式也是可能的;例如,array.array
公開的元素可以是多位元組值。
緩衝區介面的一個示例使用者是檔案物件的 write()
方法:任何可以透過緩衝區介面匯出位元組序列的物件都可以寫入檔案。雖然 write()
僅需要對傳遞給它的物件的內部內容進行只讀訪問,但其他方法(例如 readinto()
)則需要對其引數的內容進行寫入訪問。緩衝區介面允許物件有選擇地允許或拒絕匯出讀寫和只讀緩衝區。
緩衝區介面的使用者有兩種方法來獲取目標物件的緩衝區
使用正確的引數呼叫
PyObject_GetBuffer()
;使用
y*
、w*
或s*
格式程式碼之一呼叫PyArg_ParseTuple()
(或其同級函式之一)。
在這兩種情況下,不再需要緩衝區時都必須呼叫 PyBuffer_Release()
。 否則可能會導致諸如資源洩漏之類的各種問題。
緩衝區結構¶
緩衝區結構(或簡稱為“緩衝區”)作為一種將來自另一個物件的二進位制資料暴露給 Python 程式設計師的方式非常有用。它們還可以用作零複製切片機制。利用它們引用一塊記憶體的能力,可以很容易地將任何資料暴露給 Python 程式設計師。該記憶體可以是 C 擴充套件中的一個大型常量陣列,可以是操作前傳遞給作業系統庫的原始記憶體塊,也可以用於以其本機記憶體格式傳遞結構化資料。
與 Python 直譯器公開的大多數資料型別相反,緩衝區不是 PyObject
指標,而是簡單的 C 結構。 這使得它們的建立和複製非常簡單。當需要緩衝區的通用包裝器時,可以建立一個 memoryview 物件。
有關如何編寫匯出物件的簡短說明,請參見 緩衝區物件結構。 有關獲取緩衝區的資訊,請參見 PyObject_GetBuffer()
。
-
type Py_buffer¶
- 自 3.11 版本以來,是 穩定 ABI 的一部分(包括所有成員)。
-
void *buf¶
指向緩衝區欄位描述的邏輯結構開始的指標。 這可以是匯出器底層物理記憶體塊中的任何位置。 例如,使用負
strides
,該值可能指向記憶體塊的末尾。對於 連續陣列,該值指向記憶體塊的開頭。
-
PyObject *obj¶
對匯出物件的新引用。 此引用歸使用者所有,並透過
PyBuffer_Release()
自動釋放(即,引用計數遞減)並設定為NULL
。 該欄位等效於任何標準 C-API 函式的返回值。作為一種特殊情況,對於由
PyMemoryView_FromBuffer()
或PyBuffer_FillInfo()
包裝的臨時緩衝區,此欄位為NULL
。 一般來說,匯出物件不得使用此方案。
-
Py_ssize_t len¶
product(shape) * itemsize
。 對於連續陣列,這是底層記憶體塊的長度。 對於非連續陣列,如果將邏輯結構複製到連續表示形式,則它是該邏輯結構的長度。僅當透過保證連續性的請求獲取緩衝區時,訪問
((char *)buf)[0] 到 ((char *)buf)[len-1]
才有效。 在大多數情況下,這樣的請求將為PyBUF_SIMPLE
或PyBUF_WRITABLE
。
-
int readonly¶
指示緩衝區是否為只讀的指示符。 此欄位由
PyBUF_WRITABLE
標誌控制。
-
Py_ssize_t itemsize¶
單個元素的位元組大小。 與對非
NULL
format
值呼叫的struct.calcsize()
的值相同。重要例外:如果使用者請求不帶
PyBUF_FORMAT
標誌的緩衝區,則format
將設定為NULL
,但itemsize
仍然具有原始格式的值。如果存在
shape
,則等式product(shape) * itemsize == len
仍然成立,並且使用者可以使用itemsize
來瀏覽緩衝區。如果由於
PyBUF_SIMPLE
或PyBUF_WRITABLE
請求導致shape
為NULL
,則使用者必須忽略itemsize
並假設itemsize == 1
。
-
char *format¶
一個以 NULL 結尾的字串,採用
struct
模組風格語法,描述單個專案的內容。如果此值為NULL
,則假設為"B"
(無符號位元組)。此欄位由
PyBUF_FORMAT
標誌控制。
-
int ndim¶
記憶體表示為 n 維陣列的維度數。如果為
0
,則buf
指向表示標量的單個專案。在這種情況下,shape
、strides
和suboffsets
必須為NULL
。最大維度數由PyBUF_MAX_NDIM
給出。
-
Py_ssize_t *shape¶
一個長度為
ndim
的Py_ssize_t
陣列,表示記憶體作為 n 維陣列的形狀。請注意,shape[0] * ... * shape[ndim-1] * itemsize
必須等於len
。形狀值被限制為
shape[n] >= 0
。shape[n] == 0
的情況需要特別注意。有關更多資訊,請參閱複雜陣列。形狀陣列對於使用者是隻讀的。
-
Py_ssize_t *strides¶
一個長度為
ndim
的Py_ssize_t
陣列,給出在每個維度中跳過多少位元組才能到達新元素。步長值可以是任何整數。對於規則陣列,步長通常為正數,但是使用者必須能夠處理
strides[n] <= 0
的情況。有關更多資訊,請參閱複雜陣列。步長陣列對於使用者是隻讀的。
-
Py_ssize_t *suboffsets¶
一個長度為
ndim
的Py_ssize_t
陣列。如果suboffsets[n] >= 0
,則沿第 n 個維度儲存的值是指標,並且子偏移值決定在取消引用後向每個指標新增多少位元組。負的子偏移值表示不應發生取消引用(在連續的記憶體塊中步進)。如果所有子偏移量均為負數(即不需要取消引用),則此欄位必須為
NULL
(預設值)。Python Imaging Library (PIL) 使用這種型別的陣列表示形式。有關如何訪問此類陣列的元素,請參閱複雜陣列。
子偏移陣列對於使用者是隻讀的。
-
void *internal¶
這供匯出物件在內部使用。例如,匯出器可能會將其重新轉換為整數,並用於儲存有關是否必須在釋放緩衝區時釋放形狀、步長和子偏移陣列的標誌。使用者不得更改此值。
-
void *buf¶
常量
-
PyBUF_MAX_NDIM¶
記憶體表示的最大維度數。匯出器必須遵守此限制,多維緩衝區的使用者應該能夠處理多達
PyBUF_MAX_NDIM
個維度。當前設定為 64。
緩衝區請求型別¶
緩衝區通常透過 PyObject_GetBuffer()
嚮導出物件傳送緩衝區請求來獲取。由於記憶體的邏輯結構的複雜性可能差異很大,因此使用者使用 flags 引數來指定它可以處理的確切緩衝區型別。
所有 Py_buffer
欄位都由請求型別明確定義。
與請求無關的欄位¶
只讀,格式¶
- PyBUF_WRITABLE¶
控制
readonly
欄位。如果設定,匯出器必須提供可寫緩衝區,否則報告失敗。否則,匯出器可以提供只讀或可寫緩衝區,但對於所有使用者,選擇必須一致。例如,PyBUF_SIMPLE | PyBUF_WRITABLE 可用於請求簡單的可寫緩衝區。
PyBUF_WRITABLE
可以與下一節中的任何標誌進行“或”運算。由於 PyBUF_SIMPLE
定義為 0,因此 PyBUF_WRITABLE
可以用作獨立標誌來請求簡單的可寫緩衝區。
PyBUF_FORMAT
必須與除了 PyBUF_SIMPLE
之外的任何標誌進行“或”運算,因為後者已經隱含了格式 B
(無符號位元組)。 PyBUF_FORMAT
不能單獨使用。
形狀、步長、子偏移量¶
控制記憶體邏輯結構的標誌按複雜程度遞減的順序列出。請注意,每個標誌都包含其下方標誌的所有位。
請求 |
形狀 |
步長 |
子偏移量 |
---|---|---|---|
|
是 |
是 |
如果需要 |
|
是 |
是 |
NULL |
|
是 |
NULL |
NULL |
|
NULL |
NULL |
NULL |
連續性請求¶
可以顯式請求 C 或 Fortran 連續性,無論是否具有步長資訊。如果沒有步長資訊,則緩衝區必須是 C 連續的。
請求 |
形狀 |
步長 |
子偏移量 |
連續 |
---|---|---|---|---|
|
是 |
是 |
NULL |
C |
|
是 |
是 |
NULL |
F |
|
是 |
是 |
NULL |
C 或 F |
是 |
NULL |
NULL |
C |
複合請求¶
所有可能的請求都完全由上一節中標誌的某種組合定義。為了方便起見,緩衝區協議提供了常用組合作為單個標誌。
在下表中,U 表示未定義的連續性。使用者必須呼叫 PyBuffer_IsContiguous()
來確定連續性。
請求 |
形狀 |
步長 |
子偏移量 |
連續 |
只讀 |
格式 |
---|---|---|---|---|---|---|
|
是 |
是 |
如果需要 |
U |
0 |
是 |
|
是 |
是 |
如果需要 |
U |
1 或 0 |
是 |
|
是 |
是 |
NULL |
U |
0 |
是 |
|
是 |
是 |
NULL |
U |
1 或 0 |
是 |
|
是 |
是 |
NULL |
U |
0 |
NULL |
|
是 |
是 |
NULL |
U |
1 或 0 |
NULL |
|
是 |
NULL |
NULL |
C |
0 |
NULL |
|
是 |
NULL |
NULL |
C |
1 或 0 |
NULL |
複雜陣列¶
NumPy 樣式:形狀和步長¶
NumPy 樣式陣列的邏輯結構由 itemsize
、ndim
、shape
和 strides
定義。
如果 ndim == 0
,則 buf
指向的記憶體位置被解釋為大小為 itemsize
的標量。在這種情況下,shape
和 strides
均為 NULL
。
如果 strides
是 NULL
,則陣列被解釋為標準的 n 維 C 陣列。否則,使用者必須按如下方式訪問 n 維陣列
ptr = (char *)buf + indices[0] * strides[0] + ... + indices[n-1] * strides[n-1];
item = *((typeof(item) *)ptr);
如上所述,buf
可以指向實際記憶體塊中的任何位置。匯出器可以使用此函式檢查緩衝區的有效性
def verify_structure(memlen, itemsize, ndim, shape, strides, offset):
"""Verify that the parameters represent a valid array within
the bounds of the allocated memory:
char *mem: start of the physical memory block
memlen: length of the physical memory block
offset: (char *)buf - mem
"""
if offset % itemsize:
return False
if offset < 0 or offset+itemsize > memlen:
return False
if any(v % itemsize for v in strides):
return False
if ndim <= 0:
return ndim == 0 and not shape and not strides
if 0 in shape:
return True
imin = sum(strides[j]*(shape[j]-1) for j in range(ndim)
if strides[j] <= 0)
imax = sum(strides[j]*(shape[j]-1) for j in range(ndim)
if strides[j] > 0)
return 0 <= offset+imin and offset+imax+itemsize <= memlen
PIL 樣式:形狀、步長和子偏移量¶
除了常規專案外,PIL 樣式的陣列還可以包含指標,必須遵循這些指標才能到達維度中的下一個元素。例如,常規的三維 C 陣列 char v[2][2][3]
也可以被視為指向 2 個二維陣列的 2 個指標的陣列:char (*v[2])[2][3]
。在子偏移量表示中,這兩個指標可以嵌入到 buf
的開頭,指向可以位於記憶體中任何位置的兩個 char x[2][3]
陣列。
這是一個函式,當存在非 NULL
步長和子偏移量時,返回指向由 N 維索引指向的 N 維陣列中元素的指標
void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
Py_ssize_t *suboffsets, Py_ssize_t *indices) {
char *pointer = (char*)buf;
int i;
for (i = 0; i < ndim; i++) {
pointer += strides[i] * indices[i];
if (suboffsets[i] >=0 ) {
pointer = *((char**)pointer) + suboffsets[i];
}
}
return (void*)pointer;
}