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 bytesbytearray 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ư bytesbytearray 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:

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ộ đệmmemoryview.

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ành NULL. 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ặc PyBuffer_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_SIMPLE hoặc PyBUF_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ị format không phải NULL.

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, format sẽ được đặt thành NULL, nhưng itemsize vẫn có giá trị cho định dạng ban đầu.

Nếu có shape, đẳng thức product(shape) * itemsize == len vẫn giữ nguyên và người tiêu dùng có thể sử dụng itemsize để điều hướng bộ đệm.

Nếu shapeNULL do yêu cầu PyBUF_SIMPLE hoặc PyBUF_WRITABLE, người tiêu dùng phải bỏ qua itemsize và cho rằng itemsize == 1.

char *format

Một chuỗi kết thúc NULL theo cú pháp kiểu mô-đun struct mô 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, buf trỏ đế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, stridessuboffsets MUST là NULL. Số lượng kích thước tối đa được đưa ra bởi PyBUF_MAX_NDIM.

Py_ssize_t *shape

Một mảng Py_ssize_t có độ dài ndim biểu thị hình dạng của bộ nhớ dưới dạng mảng n chiều. Lưu ý rằng shape[0] * ... * shape[ndim-1] * itemsize MUST bằng len.

Giá trị hình dạng được giới hạn ở shape[n] >= 0. Trường hợp shape[n] == 0 cầ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_t có độ dài ndim cung 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_t có độ dài ndim. Nếu suboffsets[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.

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ụ

PyBUF_INDIRECT
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

nếu cần

PyBUF_STRIDES
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

NULL

PyBUF_ND
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

NULL

NULL

PyBUF_SIMPLE
Một phần của ABI ổn định kể từ phiên bản 3.11.

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

PyBUF_C_CONTIGUOUS
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

NULL

C

PyBUF_F_CONTIGUOUS
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

NULL

F

PyBUF_ANY_CONTIGUOUS
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

NULL

C hoặc F

PyBUF_ND

Đú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

PyBUF_FULL
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

nếu cần

bạn

0

Đúng

PyBUF_FULL_RO
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

nếu cần

bạn

1 hoặc 0

Đúng

PyBUF_RECORDS
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

NULL

bạn

0

Đúng

PyBUF_RECORDS_RO
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

NULL

bạn

1 hoặc 0

Đúng

PyBUF_STRIDED
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

NULL

bạn

0

NULL

PyBUF_STRIDED_RO
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

Đúng

NULL

bạn

1 hoặc 0

NULL

PyBUF_CONTIG
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đúng

NULL

NULL

C

0

NULL

PyBUF_CONTIG_RO
Một phần của ABI ổn định kể từ phiên bản 3.11.

Đú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, shapestrides.

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ả shapestrides đều là NULL.

Nếu stridesNULL 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  % kích thước mục:
        trả về Sai
    nếu offset < 0 hoặc offset+itemsize > memlen:
        trả về Sai
    nếu  (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  không định hình cũng không sải bước
    nếu  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  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*);
}