緩沖協議?
在 Python 中可使用一些對象來包裝對底層內存數組或稱 緩沖 的訪問。此類對象包括內置的 bytes 和 bytearray 以及一些如 array.array 這樣的擴展類型。第三方庫也可能會為了特殊的目的而定義它們自己的類型,例如用于圖像處理和數值分析等。
雖然這些類型中的每一種都有自己的語義,但它們具有由可能較大的內存緩沖區支持的共同特征。 在某些情況下,希望直接訪問該緩沖區而無需中間復制。
Python 以 緩沖協議 的形式在 C 層級上提供這樣的功能。 此協議包括兩個方面:
在生產者這一方面,該類型的協議可以導出一個“緩沖區接口”,允許公開它的底層緩沖區信息。該接口的描述信息在 Buffer Object Structures 一節中;
在消費者一側,有幾種方法可用于獲得指向對象的原始底層數據的指針(例如一個方法的形參)。
一些簡單的對象例如 bytes 和 bytearray 會以面向字節的形式公開它們的底層緩沖區。 也可能會用其他形式;例如 array.array 所公開的元素可以是多字節值。
緩沖區接口的消費者的一個例子是文件對象的 write() 方法:任何可以輸出為一系列字節流的對象可以被寫入文件。然而 write() 方法只需要對于傳入對象的只讀權限,其他的方法,如 readinto() 需要參數內容的寫入權限。緩沖區接口使得對象可以選擇性地允許或拒絕讀寫或只讀緩沖區的導出。
對于緩沖接口的消費者而言,有兩種方式來獲取一個目的對象的緩沖。
使用正確的參數來調用
PyObject_GetBuffer()? 函數調用
PyArg_ParseTuple()(或其同級對象之一) 并傳入y*,w*ors*格式代碼 中的一個。
在這兩種情況下,當不再需要緩沖區時必須調用 PyBuffer_Release()?。如果此操作失敗,可能會導致各種問題,例如資源泄漏。
緩沖區結構?
緩沖區結構(或者簡單地稱為“buffers”)對于將二進制數據從另一個對象公開給Python程序員非常有用。它們還可以用作零拷貝切片機制。使用它們引用內存塊的能力,可以很容易地將任何數據公開給Python程序員。內存可以是C擴展中的一個大的常量數組,也可以是在傳遞到操作系統庫之前用于操作的原始內存塊,或者可以用來傳遞本機內存格式的結構化數據。
與 Python 解釋器公開的大多部數據類型不同,緩沖區不是 PyObject 指針而是簡單的 C 結構。 這使得它們可以非常簡單地創建和復制。 當需要為緩沖區加上泛型包裝器時,可以創建一個 內存視圖 對象。
有關如何編寫并導出對象的簡短說明,請參閱 緩沖區對象結構。 要獲取緩沖區對象,請參閱 PyObject_GetBuffer()。
-
Py_buffer? -
void *
buf? 指向由緩沖區字段描述的邏輯結構開始的指針。 這可以是導出程序底層物理內存塊中的任何位置。 例如,使用負的
strides值可能指向內存塊的末尾。對于 contiguous ,‘鄰接’數組,值指向內存塊的開頭。
-
void *
obj? 對導出對象的新引用。 該引用歸使用者所有,并由
PyBuffer_Release()自動遞減并設置為NULL。 該字段等于任何標準 C-API 函數的返回值。作為一種特殊情況,對于由
PyMemoryView_FromBuffer()或PyBuffer_FillInfo()包裝的 temporary 緩沖區,此字段為NULL。 通常,導出對象不得使用此方案。
-
Py_ssize_t
len? product(shape) * itemsize。對于連續數組,這是基礎內存塊的長度。對于非連續數組,如果邏輯結構復制到連續表示形式,則該長度將具有該長度。僅當緩沖區是通過保證連續性的請求獲取時,才訪問
((char *)buf)[0] up to ((char *)buf)[len-1]時才有效。在大多數情況下,此類請求將為PyBUF_SIMPLE或PyBUF_WRITABLE。
-
int
readonly? 緩沖區是否為只讀的指示器。此字段由
PyBUF_WRITABLE標志控制。
-
Py_ssize_t
itemsize? 單個元素的項大小(以字節為單位)。與
struct.calcsize()調用非NULLformat的值相同。重要例外:如果使用者請求的緩沖區沒有
PyBUF_FORMAT標志,format將設置為NULL,但itemsize仍具有原始格式的值。如果
shape存在,則相等的product(shape) * itemsize == len仍然存在,使用者可以使用itemsize來導航緩沖區。如果
shape是NULL,因為結果為PyBUF_SIMPLE或PyBUF_WRITABLE請求,則使用者必須忽略itemsize,并假設itemsize == 1。
-
const char *
format? 在
struct模塊樣式語法中 NUL 字符串,描述單個項的內容。如果這是NULL,則假定為``"B"`` (無符號字節) 。此字段由
PyBUF_FORMAT標志控制。
-
int
ndim? 內存表示為 n 維數組的維數。 如果是``0``,
buf指向表示標量的單個項目。 在這種情況下,shape、strides和suboffsets必須是``NULL`` 。宏
PyBUF_MAX_NDIM將最大維度數限制為 64。 導出程序必須遵守這個限制,多維緩沖區的使用者應該能夠處理最多PyBUF_MAX_NDIM維度。
-
Py_ssize_t *
shape? 一個長度為
Py_ssize_t的數組ndim表示作為 n 維數組的內存形狀。 請注意,shape[0] * ... * shape[ndim-1] * itemsize必須等于len。Shape 形狀數組中的值被限定在
shape[n] >= 0。shape[n] == 0這一情形需要特別注意。更多信息請參閱 complex arrays 。shape數組對于使用者來說是只讀的。
-
Py_ssize_t *
strides? 一個長度為
Py_ssize_t的數組ndim給出要跳過的字節數以獲取每個尺寸中的新元素。Stride 步幅數組中的值可以為任何整數。對于常規數組,步幅通常為正數,但是使用者必須能夠處理
strides[n] <= 0的情況。更多信息請參閱 complex arrays 。strides數組對用戶來說是只讀的。
-
Py_ssize_t *
suboffsets? 一個長度為
ndim類型為Py_ssize_t的數組 。如果suboffsets[n] >= 0,則第 n 維存儲的是指針,suboffset 值決定了解除引用時要給指針增加多少字節的偏移。suboffset 為負值,則表示不應解除引用(在連續內存塊中移動)。如果所有子偏移均為負(即無需取消引用),則此字段必須為
NULL(默認值)。Python Imaging Library (PIL) 中使用了這種數組的表達方式。請參閱 complex arrays 來了解如何從這樣一個數組中訪問元素。
suboffsets 數組對于使用者來說是只讀的。
-
void *
internal? 供輸出對象內部使用。比如可能被導出器重組為一個整數,用于存儲一個標志,標明在緩沖區釋放時是否必須釋放 shape、strides 和 suboffsets 數組。緩沖區用戶 不得 修改該值。
-
void *
緩沖區請求的類型?
通常,通過 PyObject_GetBuffer() 向輸出對象發送緩沖區請求,即可獲得緩沖區。由于內存的邏輯結構復雜,可能會有很大差異,緩沖區使用者可用 flags 參數指定其能夠處理的緩沖區具體類型。
所有 Py_buffer 字段均由請求類型明確定義。
只讀,格式?
PyBUF_WRITABLE 可以和下一節的所有標志聯用。由于 PyBUF_SIMPLE 定義為 0,所以 PyBUF_WRITABLE 可以作為一個獨立的標志,用于請求一個簡單的可寫緩沖區。
PyBUF_FORMAT 可以被設為除了 PyBUF_SIMPLE 之外的任何標志。 后者已經按暗示了``B``(無符號字節串)格式。
形狀,步幅,子偏移量?
控制內存邏輯結構的標志按照復雜度的遞減順序列出。注意,每個標志包含它下面的所有標志。
請求 |
形狀 |
步幅 |
子偏移量 |
|---|---|---|---|
|
是 |
是 |
如果需要的話 |
|
是 |
是 |
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] 數組。
Here is a function that returns a pointer to the element in an N-D array
pointed to by an N-dimensional index when there are both non-NULL strides
and suboffsets:
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;
}
緩沖區相關函數?
-
int
PyObject_CheckBuffer(PyObject?*obj)? 如果 obj 支持緩沖區接口,則返回
1,否則返回0。 返回1時不保證PyObject_GetBuffer()一定成功。本函數一定調用成功。
-
int
PyObject_GetBuffer(PyObject?*exporter, Py_buffer?*view, int?flags)? Send a request to exporter to fill in view as specified by flags. If the exporter cannot provide a buffer of the exact type, it MUST raise
PyExc_BufferError, setview->objtoNULLand return-1.成功時,填充 view,將
view->obj設為對 exporter 的新引用并返回 0。 在使用將請求重定向到單一對象的鏈式緩沖區提供器的情況下,view->obj可以引用該對象而不是 exporter (參見 緩沖區對象結構)。PyObject_GetBuffer()必須與PyBuffer_Release()同時調用成功,類似于malloc()和free()。因此,消費者程序用完緩沖區后,PyBuffer_Release()必須保證被調用一次。
-
void
PyBuffer_Release(Py_buffer?*view)? Release the buffer view and decrement the reference count for
view->obj. This function MUST be called when the buffer is no longer being used, otherwise reference leaks may occur.若該函數針對的緩沖區不是通過
PyObject_GetBuffer()獲得的,將會出錯。
-
Py_ssize_t
PyBuffer_SizeFromFormat(const char?*)? Return the implied
itemsizefromformat. This function is not yet implemented.
-
int
PyBuffer_IsContiguous(Py_buffer?*view, char?order)? 如果 view 定義的內存是 C 風格(order 為
'C')或 Fortran 風格(order 為'F') contiguous 或其中之一(order 是'A'),則返回1。否則返回0。該函數總會成功。
-
void*
PyBuffer_GetPointer(Py_buffer?*view, Py_ssize_t?*indices)? 獲取給定 view 內的 indices 所指向的內存區域。indices 必須指向一個
view->ndim索引的數組。
-
int
PyBuffer_FromContiguous(Py_buffer?*view, void?*buf, Py_ssize_t?len, char?fort)? 從 buf 復制連續的 len 字節到 view 。fort 可以是
'C'或'F'``(對應于 C 風格或 Fortran 風格的順序)。成功時返回 ``0,錯誤時返回-1。
-
int
PyBuffer_ToContiguous(void?*buf, Py_buffer?*src, Py_ssize_t?len, char?order)? 從 src 復制 len 字節到 buf ,成為連續字節串的形式。order 可以是
'C'或'F'或'A'``(對應于 C 風格、Fortran 風格的順序或其中任意一種)。成功時返回 ``0,出錯時返回-1。如果 len != src->len 則此函數將報錯。
-
void
PyBuffer_FillContiguousStrides(int?ndims, Py_ssize_t?*shape, Py_ssize_t?*strides, int?itemsize, char?order)? 用給定形狀的 contiguous 字節串數組 (如果 order 為
'C'則為 C 風格,如果 order 為'F'則為 Fortran 風格) 來填充 strides 數組,每個元素具有給定的字節數。
-
int
PyBuffer_FillInfo(Py_buffer?*view, PyObject?*exporter, void?*buf, Py_ssize_t?len, int?readonly, int?flags)? 處理導出程序的緩沖區請求,該導出程序要暴露大小為 len 的 buf ,并根據 readonly 設置可寫性。bug 被解釋為一個無符號字節序列。
參數 flags 表示請求的類型。該函數總是按照 flag 指定的內容填入 view,除非 buf 設為只讀,并且 flag 中設置了
PyBUF_WRITABLE標志。On success, set
view->objto a new reference to exporter and return 0. Otherwise, raisePyExc_BufferError, setview->objtoNULLand return-1;如果此函數用作 getbufferproc 的一部分,則 exporter 必須設置為導出對象,并且必須在未修改的情況下傳遞 flags。否則,exporter 必須是
NULL。
