Giao thức đệm¶
Một số đối tượng nhất định có sẵn trong Python có quyền truy cập vào mảng bộ nhớ cơ bản hoặc buffer. Các đối tượng như vậy bao gồm bytes và bytearray tích hợp sẵn và một số loại tiện ích mở rộng như array.array. Thư viện của bên thứ ba có thể xác định loại riêng của họ cho các mục đích đặc biệt, chẳng hạn như xử lý hình ảnh hoặc phân tích số.
Mặc dù mỗi loại này có ngữ nghĩa riêng nhưng chúng có chung đặc điểm là được hỗ trợ bởi bộ nhớ đệm có thể lớn. Sau đó, trong một số trường hợp, mong muốn truy cập bộ đệm đó một cách trực tiếp và không cần sao chép trung gian.
Python cung cấp cơ sở như vậy ở cấp độ C và Python dưới dạng buffer protocol. Giao thức này có hai mặt:
về phía nhà sản xuất, một loại có thể xuất "giao diện bộ đệm" cho phép các đối tượng thuộc loại đó hiển thị thông tin về bộ đệm cơ bản của chúng. Giao diện này được mô tả trong phần Cấu trúc đối tượng đệm; đối với Python, hãy xem Mô phỏng các loại bộ đệm.
về phía người tiêu dùng, có sẵn một số phương tiện để lấy con trỏ tới dữ liệu cơ bản thô của một đối tượng (ví dụ: tham số phương thức). Đối với Python, hãy xem
memoryview.
Các đối tượng đơn giản như bytes và bytearray hiển thị bộ đệm cơ bản của chúng ở dạng hướng byte. Có thể có các hình thức khác; ví dụ: các phần tử được hiển thị bởi array.array có thể là giá trị nhiều byte.
Một ví dụ tiêu dùng của giao diện bộ đệm là phương thức write() của các đối tượng tệp: bất kỳ đối tượng nào có thể xuất một chuỗi byte thông qua giao diện bộ đệm đều có thể được ghi vào một tệp. Trong khi write() chỉ cần quyền truy cập chỉ đọc vào nội dung bên trong của đối tượng được truyền cho nó, thì các phương thức khác như readinto() cần quyền truy cập ghi vào nội dung đối số của chúng. Giao diện bộ đệm cho phép các đối tượng cho phép hoặc từ chối có chọn lọc việc xuất bộ đệm đọc-ghi và chỉ đọc.
Có hai cách để người tiêu dùng giao diện bộ đệm có được bộ đệm trên đối tượng đích:
gọi
PyObject_GetBuffer()với các thông số phù hợp;gọi
PyArg_ParseTuple()(hoặc một trong những anh chị em của nó) bằng một trong cácy*,w*hoặcs*format codes.
Trong cả hai trường hợp, PyBuffer_Release() phải được gọi khi không cần bộ đệm nữa. Không làm như vậy có thể dẫn đến nhiều vấn đề khác nhau như rò rỉ tài nguyên.
Added in version 3.12: Giao thức bộ đệm hiện có thể truy cập được bằng Python, xem Mô phỏng các loại bộ đệm và memoryview.
Cấu trúc đệm¶
Cấu trúc bộ đệm (hoặc đơn giản là "bộ đệm") rất hữu ích như một cách để hiển thị dữ liệu nhị phân từ một đối tượng khác cho lập trình viên Python. Chúng cũng có thể được sử dụng như một cơ chế cắt không sao chép. Sử dụng khả năng tham chiếu một khối bộ nhớ, có thể hiển thị bất kỳ dữ liệu nào cho lập trình viên Python khá dễ dàng. Bộ nhớ có thể là một mảng lớn, không đổi trong phần mở rộng C, nó có thể là một khối bộ nhớ thô để thao tác trước khi chuyển đến thư viện hệ điều hành hoặc nó có thể được sử dụng để truyền dữ liệu có cấu trúc ở định dạng gốc trong bộ nhớ.
Trái ngược với hầu hết các kiểu dữ liệu được trình thông dịch Python đưa ra, bộ đệm không phải là con trỏ PyObject mà là cấu trúc C đơn giản. Điều này cho phép chúng được tạo và sao chép rất đơn giản. Khi cần một trình bao bọc chung xung quanh bộ đệm, một đối tượng memoryview có thể được tạo.
Để biết hướng dẫn ngắn gọn về cách viết đối tượng xuất, hãy xem Buffer Object Structures. Để có được bộ đệm, hãy xem PyObject_GetBuffer().
-
type Py_buffer¶
- Một phần của ABI ổn định (bao gồm tất cả các thành viên) kể từ phiên bản 3.11.
-
void *buf¶
Một con trỏ tới điểm bắt đầu của cấu trúc logic được mô tả bởi các trường đệm. Đây có thể là bất kỳ vị trí nào trong khối bộ nhớ vật lý cơ bản của nhà xuất khẩu. Ví dụ: với
stridesâm, giá trị có thể trỏ đến cuối khối bộ nhớ.Đối với mảng contiguous, giá trị trỏ đến phần đầu của khối bộ nhớ.
-
PyObject *obj¶
Một tham chiếu mới tới đối tượng xuất khẩu. Tham chiếu này thuộc sở hữu của người tiêu dùng và được giải phóng tự động (tức là số lượng tham chiếu giảm đi) và được
PyBuffer_Release()đặt thànhNULL. Trường này tương đương với giá trị trả về của bất kỳ hàm C-API tiêu chuẩn nào.Trong trường hợp đặc biệt, đối với bộ đệm temporary được bao bọc bởi
PyMemoryView_FromBuffer()hoặcPyBuffer_FillInfo(), trường này làNULL. Nói chung, việc xuất các đối tượng MUST NOT sử dụng sơ đồ này.
-
Py_ssize_t len¶
product(shape) * itemsize. Đối với các mảng liền kề, đây là độ dài của khối bộ nhớ cơ bản. Đối với các mảng không liền kề, đó là độ dài mà cấu trúc logic sẽ có nếu nó được sao chép sang một biểu diễn liền kề.Việc truy cập
((char *)buf)[0] up to ((char *)buf)[len-1]chỉ hợp lệ nếu bộ đệm được lấy bằng yêu cầu đảm bảo tính liên tục. Trong hầu hết các trường hợp, yêu cầu như vậy sẽ làPyBUF_SIMPLEhoặcPyBUF_WRITABLE.
-
int readonly¶
Một chỉ báo cho biết bộ đệm có ở chế độ chỉ đọc hay không. Trường này được kiểm soát bởi cờ
PyBUF_WRITABLE.
-
Py_ssize_t itemsize¶
Kích thước mục tính bằng byte của một phần tử. Tương tự như giá trị của
struct.calcsize()được gọi trên các giá trịformatkhông phảiNULL.Ngoại lệ quan trọng: Nếu người tiêu dùng yêu cầu bộ đệm không có cờ
PyBUF_FORMAT,formatsẽ được đặt thànhNULL, nhưngitemsizevẫn có giá trị cho định dạng ban đầu.Nếu có
shape, đẳng thứcproduct(shape) * itemsize == lenvẫn giữ nguyên và người tiêu dùng có thể sử dụngitemsizeđể điều hướng bộ đệm.Nếu
shapelàNULLdo yêu cầuPyBUF_SIMPLEhoặcPyBUF_WRITABLE, người tiêu dùng phải bỏ quaitemsizevà cho rằngitemsize == 1.
-
char *format¶
Một chuỗi kết thúc NULL theo cú pháp kiểu mô-đun
structmô tả nội dung của một mục. Nếu đây làNULL, thì giả sử"B"(byte không dấu).Trường này được kiểm soát bởi cờ
PyBUF_FORMAT.
-
int ndim¶
Số chiều mà bộ nhớ biểu thị dưới dạng mảng n chiều. Nếu là
0,buftrỏ đến một mục duy nhất đại diện cho đại lượng vô hướng. Trong trường hợp này,shape,stridesvàsuboffsetsMUST làNULL. Số lượng kích thước tối đa được đưa ra bởiPyBUF_MAX_NDIM.
-
Py_ssize_t *shape¶
Một mảng
Py_ssize_tcó độ dàindimbiểu thị hình dạng của bộ nhớ dưới dạng mảng n chiều. Lưu ý rằngshape[0] * ... * shape[ndim-1] * itemsizeMUST bằnglen.Giá trị hình dạng được giới hạn ở
shape[n] >= 0. Trường hợpshape[n] == 0cần được chú ý đặc biệt. Xem complex arrays để biết thêm thông tin.Mảng hình dạng chỉ đọc cho người tiêu dùng.
-
Py_ssize_t *strides¶
Một mảng
Py_ssize_tcó độ dàindimcung cấp số byte cần bỏ qua để đến phần tử mới trong mỗi thứ nguyên.Giá trị bước tiến có thể là số nguyên bất kỳ. Đối với mảng thông thường, các bước tiến thường là dương, nhưng MUST tiêu dùng có thể xử lý trường hợp
strides[n] <= 0. Xem complex arrays để biết thêm thông tin.Mảng sải bước ở chế độ chỉ đọc cho người tiêu dùng.
-
Py_ssize_t *suboffsets¶
Một mảng
Py_ssize_tcó độ dàindim. Nếusuboffsets[n] >= 0, các giá trị được lưu dọc theo chiều thứ n là các con trỏ và giá trị bù phụ cho biết số byte cần thêm vào mỗi con trỏ sau khi hủy tham chiếu. Giá trị bù phụ âm cho biết rằng không xảy ra việc hủy tham chiếu (di chuyển trong khối bộ nhớ liền kề).Nếu tất cả các phần bù phụ đều âm (tức là không cần hủy tham chiếu) thì trường này phải là
NULL(giá trị mặc định).Kiểu biểu diễn mảng này được Thư viện hình ảnh Python (PIL) sử dụng. Xem complex arrays để biết thêm thông tin về cách truy cập các phần tử của một mảng như vậy.
Mảng suboffsets chỉ đọc cho người tiêu dùng.
-
void *internal¶
Điều này được sử dụng nội bộ bởi đối tượng xuất khẩu. Ví dụ: điều này có thể được nhà xuất khẩu chuyển lại thành một số nguyên và được sử dụng để lưu trữ các cờ về việc các mảng hình dạng, bước tiến và các mảng phụ có phải được giải phóng hay không khi bộ đệm được giải phóng. Người tiêu dùng MUST NOT thay đổi giá trị này.
-
void *buf¶
Hằng số:
-
PyBUF_MAX_NDIM¶
- Một phần của ABI ổn định kể từ phiên bản 3.11.
Số lượng kích thước tối đa mà bộ nhớ đại diện. Các nhà xuất khẩu MUST tôn trọng giới hạn này, người tiêu dùng bộ đệm đa chiều SHOULD có thể xử lý các kích thước lên tới
PyBUF_MAX_NDIM. Hiện tại được đặt thành 64.
Các loại yêu cầu bộ đệm¶
Bộ đệm thường được lấy bằng cách gửi yêu cầu bộ đệm đến đối tượng xuất thông qua PyObject_GetBuffer(). Vì độ phức tạp của cấu trúc logic của bộ nhớ có thể thay đổi đáng kể nên người dùng sử dụng đối số flags để chỉ định loại bộ đệm chính xác mà nó có thể xử lý.
Tất cả các trường Py_buffer đều được xác định rõ ràng theo loại yêu cầu.
các trường độc lập với yêu cầu¶
Các trường sau không bị ảnh hưởng bởi flags và phải luôn được điền giá trị chính xác: obj, buf, len, itemsize, ndim.
chỉ đọc, định dạng¶
- PyBUF_WRITABLE¶
- Một phần của ABI ổn định kể từ phiên bản 3.11.
Kiểm soát trường
readonly. Nếu được đặt, nhà xuất khẩu MUST sẽ cung cấp bộ đệm có thể ghi nếu không sẽ báo cáo lỗi. Mặt khác, nhà xuất khẩu MAY cung cấp bộ đệm chỉ đọc hoặc có thể ghi, nhưng lựa chọn MUST phải nhất quán cho tất cả người tiêu dùng. Ví dụ: PyBUF_SIMPLE | PyBUF_WRITABLE có thể được sử dụng để yêu cầu một bộ đệm có thể ghi đơn giản.
- PyBUF_WRITEABLE¶
Đây là bí danh soft deprecated của
PyBUF_WRITABLE.
- PyBUF_FORMAT¶
- Một phần của ABI ổn định kể từ phiên bản 3.11.
Kiểm soát trường
format. Nếu được đặt, trường MUST này sẽ được điền chính xác. Ngược lại, trường này MUST sẽ làNULL.
PyBUF_WRITABLE có thể được |'d đối với bất kỳ cờ nào trong phần tiếp theo. Vì PyBUF_SIMPLE được định nghĩa là 0 nên PyBUF_WRITABLE có thể được sử dụng làm cờ độc lập để yêu cầu một bộ đệm có thể ghi đơn giản.
PyBUF_FORMAT phải là |'d đối với bất kỳ cờ nào ngoại trừ PyBUF_SIMPLE, vì cờ sau đã bao hàm định dạng B (byte không dấu). PyBUF_FORMAT không thể được sử dụng riêng lẻ.
hình dạng, bước tiến, phần phụ¶
Các cờ kiểm soát cấu trúc logic của bộ nhớ được liệt kê theo thứ tự phức tạp giảm dần. Lưu ý rằng mỗi cờ chứa tất cả các bit của cờ bên dưới nó.
Lời yêu cầu |
hình dạng |
bước tiến |
phần bù đắp phụ |
|---|---|---|---|
|
Đúng |
Đúng |
nếu cần |
|
Đúng |
Đúng |
NULL |
|
Đúng |
NULL |
NULL |
|
NULL |
NULL |
NULL |
yêu cầu tiếp giáp¶
C hoặc Fortran contiguity có thể được yêu cầu rõ ràng, có và không có thông tin về bước tiến. Nếu không có thông tin bước tiến, bộ đệm phải liền kề với C.
Lời yêu cầu |
hình dạng |
bước tiến |
phần bù đắp phụ |
tiếp giáp |
|---|---|---|---|---|
|
Đúng |
Đúng |
NULL |
C |
|
Đúng |
Đúng |
NULL |
F |
|
Đúng |
Đúng |
NULL |
C hoặc F |
Đúng |
NULL |
NULL |
C |
yêu cầu ghép¶
Tất cả các yêu cầu có thể được xác định đầy đủ bằng sự kết hợp nào đó của các cờ trong phần trước. Để thuận tiện, giao thức đệm cung cấp các kết hợp được sử dụng thường xuyên dưới dạng các cờ đơn.
Trong bảng sau U là viết tắt của sự liên tục không xác định. Người tiêu dùng sẽ phải gọi PyBuffer_IsContiguous() để xác định sự liên tục.
Lời yêu cầu |
hình dạng |
bước tiến |
phần bù đắp phụ |
tiếp giáp |
chỉ đọc |
định dạng |
|---|---|---|---|---|---|---|
|
Đúng |
Đúng |
nếu cần |
bạn |
0 |
Đúng |
|
Đúng |
Đúng |
nếu cần |
bạn |
1 hoặc 0 |
Đúng |
|
Đúng |
Đúng |
NULL |
bạn |
0 |
Đúng |
|
Đúng |
Đúng |
NULL |
bạn |
1 hoặc 0 |
Đúng |
|
Đúng |
Đúng |
NULL |
bạn |
0 |
NULL |
|
Đúng |
Đúng |
NULL |
bạn |
1 hoặc 0 |
NULL |
|
Đúng |
NULL |
NULL |
C |
0 |
NULL |
|
Đúng |
NULL |
NULL |
C |
1 hoặc 0 |
NULL |
Mảng phức tạp¶
Kiểu NumPy: hình dạng và bước tiến¶
Cấu trúc logic của mảng kiểu NumPy được xác định bởi itemsize, ndim, shape và strides.
Nếu ndim == 0, vị trí bộ nhớ được trỏ bởi buf được hiểu là vô hướng có kích thước itemsize. Trong trường hợp đó, cả shape và strides đều là NULL.
Nếu strides là NULL thì mảng được hiểu là mảng C n chiều tiêu chuẩn. Mặt khác, người tiêu dùng phải truy cập vào mảng n chiều như sau:
ptr = (char *)buf + indices[0] * sải bước[0] + ... + chỉ số[n-1] * sải bước[n-1];
mục = *((typeof(item) *)ptr);
Như đã lưu ý ở trên, buf có thể trỏ đến bất kỳ vị trí nào trong khối bộ nhớ thực. Nhà xuất khẩu có thể kiểm tra tính hợp lệ của bộ đệm bằng chức năng này:
def verify_structure(memlen, itemsize, ndim, hình dạng, bước tiến, offset):
"""Xác minh rằng các tham số đại diện cho một mảng hợp lệ trong
giới hạn của bộ nhớ được phân bổ:
char *mem: bắt đầu khối bộ nhớ vật lý
memlen: chiều dài của khối bộ nhớ vật lý
bù đắp: (char *)buf - mem
"""
nếu bù % kích thước mục:
trả về Sai
nếu offset < 0 hoặc offset+itemsize > memlen:
trả về Sai
nếu có (v % kích thước vật phẩm cho v theo bước):
trả về Sai
nếu ndim <= 0:
trả về ndim == 0 và không định hình cũng không sải bước
nếu có dạng 0:
trả về Đúng
imin = sum(sải bước[j]*(shape[j]-1) cho j trong phạm vi(ndim)
nếu sải bước[j] <= 0)
imax = sum(sải bước[j]*(shape[j]-1) cho j trong phạm vi(ndim)
nếu sải bước[j] > 0)
trả về 0 <= offset+imin và offset+imax+itemsize <= memlen
kiểu PIL: hình dạng, bước tiến và phần phụ¶
Ngoài các mục thông thường, mảng kiểu PIL có thể chứa các con trỏ phải được tuân theo để đến phần tử tiếp theo trong một thứ nguyên. Ví dụ: mảng C ba chiều thông thường char v[2][2][3] cũng có thể được xem dưới dạng mảng gồm 2 con trỏ tới 2 mảng hai chiều: char (*v[2])[2][3]. Trong biểu diễn các tập hợp con, hai con trỏ đó có thể được nhúng ở đầu buf, trỏ đến hai mảng char x[2][3] có thể được đặt ở bất kỳ đâu trong bộ nhớ.
Đây là hàm trả về một con trỏ tới phần tử trong mảng N-D được trỏ đến bởi chỉ mục N chiều khi có cả bước tiến và tập hợp con không phải NULL:
void *get_item_pointer(int ndim, void *buf, Py_ssize_t *sải bước,
Py_ssize_t *suboffsets, Py_ssize_t *indices) {
char *pointer = (char*)buf;
int tôi;
for (i = 0; i < ndim; i++) {
con trỏ += sải bước[i] * chỉ số[i];
if (suboffsets[i] >=0 ) {
con trỏ = *((char**)con trỏ) + suboffsets[i];
}
}
con trỏ trả về (void*);
}