Python C API Py_buffer Struct Fields Explained

Python's buffer protocol provides a standardized C-level interface for zero-copy data sharing between Python objects, which is critical for high-performance computing, image processing, and data manipulation libraries like NumPy. At the center of this protocol is the Py_buffer struct, a data structure that describes the underlying layout, format, and properties of an object's memory. This article breaks down all the fields defined in the Py_buffer struct and explains their specific roles in exposing and interacting with native memory buffers.


The Py_buffer Struct Definition

Defined in Python's header files (typically <Python.h> or <bufferobject.h>), the Py_buffer struct consists of the following standard fields:

typedef struct bufferinfo {
    void *buf;
    PyObject *obj;
    Py_ssize_t len;
    Py_ssize_t itemsize;
    int readonly;
    int ndim;
    char *format;
    Py_ssize_t *shape;
    Py_ssize_t *strides;
    Py_ssize_t *suboffsets;
    void *internal;
} Py_buffer;

Core Data and Buffer Size Fields

void *buf

The buf field is a generic pointer to the logical beginning of the memory block being exposed. Depending on how the strides are configured, this does not necessarily point to the lowest physical memory address of the allocated block, but rather the element at index (0, 0, ..., 0).

Py_ssize_t len

This field contains the total length of the logical memory block in bytes. It represents the total size that the consumer can access, calculated generally as itemsize multiplied by the total number of elements.

int readonly

An integer acting as a boolean flag. A value of 1 indicates that the buffer is read-only and attempts to write to buf should be prevented. A value of 0 indicates that the buffer supports read and write operations.


Element and Type Metadata

Py_ssize_t itemsize

This field stores the storage size of a single element in bytes. For an array of standard 32-bit signed integers, itemsize would be 4.

char *format

A NUL-terminated C string that describes the data type of each individual element using Python's struct module syntax (PEP 3118 format strings). For example:


Multidimensional Layout Fields

int ndim

The number of dimensions (axes) represented by the buffer. A flat block of memory has an ndim of 1, whereas a 2D matrix has an ndim of 2. A scalar has an ndim of 0.

Py_ssize_t *shape

An array of ndim integers defining the size of the buffer along each dimension. For example, a 3x4 matrix would have a shape array containing {3, 4}. If ndim is 0, this field should be NULL.

Py_ssize_t *strides

An array of ndim integers representing the number of bytes needed to jump to the next element along each corresponding dimension.

Py_ssize_t *suboffsets

An array of ndim integers used for arrays of pointers (non-contiguous, nested slices), common in languages like C when handling multidimensional arrays defined as type**.


Ownership and Lifecycle Fields

PyObject *obj

A pointer to the exporting Python object that owns the underlying memory. When a consumer requests a buffer using functions like PyObject_GetBuffer(), this field stores a new reference to the source object (Py_INCREF is called). When the consumer is finished with the buffer, passing the struct to PyBuffer_Release() decrements this reference to ensure safe garbage collection.

void *internal

A private pointer reserved for the exporting object. The consumer must never modify or inspect this field. It is often used by the exporting object to track internal state, dynamic allocations for shape or strides, or custom memory managers that need cleanup upon release.