Attached is the PEP.
:PEP: XXX :Title: Revising the buffer protocol :Version: $Revision: $ :Last-Modified: $Date: $ :Author: Travis Oliphant <[EMAIL PROTECTED]> :Status: Draft :Type: Standards Track :Content-Type: text/x-rst :Created: 28-Aug-2006 :Python-Version: 3000
Abstract ======== This PEP proposes re-designing the buffer API (PyBufferProcs function pointers) to improve the way Python allows memory sharing in Python 3.0 In particular, it is proposed that the multiple-segment and character buffer portions of the buffer API be eliminated and additional function pointers be provided to allow sharing any multi-dimensional nature of the memory and what data-format the memory contains. Rationale ========= The buffer protocol allows different Python types to exchange a pointer to a sequence of internal buffers. This functionality is *extremely* useful for sharing large segments of memory between different high-level objects, but it is too limited and has issues. 1. There is the little (never?) used "sequence-of-segments" option (bf_getsegcount) 2. There is the apparently redundant character-buffer option (bf_getcharbuffer) 3. There is no way for a consumer to tell the buffer-API-exporting object it is "finished" with its view of the memory and therefore no way for the exporting object to be sure that it is safe to reallocate the pointer to the memory that it owns (for example, the array object reallocating its memory after sharing it with the buffer object which held the original pointer led to the infamous buffer-object problem). 4. Memory is just a pointer with a length. There is no way to describe what is "in" the memory (float, int, C-structure, etc.) 5. There is no shape information provided for the memory. But, several array-like Python types could make use of a standard way to describe the shape-interpretation of the memory (wxPython, GTK, pyQT, CVXOPT, PyVox, Audio and Video Libraries, ctypes, NumPy, data-base interfaces, etc.) 6. There is no way to share discontiguous memory (except through the sequence of segments notion). There are two widely used libraries that use the concept of discontiguous memory: PIL and NumPy. Their view of discontiguous arrays is different, though. This buffer interface allows sharing of either memory model. Exporters will only use one approach and consumers may choose to support discontiguous arrays of each type however they choose. NumPy uses the notion of constant striding in each dimension as its basic concept of an array. With this concept, a simple sub-region of a larger array can be described without copying the data. T Thus, stride information is the additional information that must be shared. The PIL uses a more opaque memory representation. Sometimes an image is contained in a contiguous segment of memory, but sometimes it is contained in an array of pointers to the contiguous segments (usually lines) of the image. The PIL is where the idea of multiple buffer segments in the original buffer interface came from. NumPy's strided memory model is used more often in computational libraries and because it is so simple it makes sense to support memory sharing using this model. The PIL memory model is used often in C-code where a 2-d array can be then accessed using double pointer indirection: e.g. image[i][j]. The buffer interface should allow the object to export either of these memory models. Consumers are free to either require contiguous memory or write code to handle either memory model. Proposal Overview ================= * Eliminate the char-buffer and multiple-segment sections of the buffer-protocol. * Unify the read/write versions of getting the buffer. * Add a new function to the interface that should be called when the consumer object is "done" with the view. * Add a new variable to allow the interface to describe what is in memory (unifying what is currently done now in struct and array) * Add a new variable to allow the protocol to share shape information * Add a new variable for sharing stride information * Add a new mechanism for sharing array of arrays. * Fix all objects in the core and the standard library to conform to the new interface * Extend the struct module to handle more format specifiers Specification ============= Change the PyBufferProcs structure to :: typedef struct { getbufferproc bf_getbuffer releasebufferproc bf_releasebuffer } :: typedef PyObject *(*getbufferproc)(PyObject *obj, void **buf, Py_ssize_t *len, int *writeable, char **format, int *ndims, Py_ssize_t **shape, Py_ssize_t **strides, void **segments) All variables except the first are optional. Use NULL for all un-needed variables. Thus, this function can be called to get only the desired information from an object. NULL is returned on failure. On success an object-specific view is returned (which may just be a borrowed reference to obj). This view should be passed to bf_releasebuffer when the consumer is done with the view. buf a pointer to the start of the memory for the object is returned in ``*buf`` len adress of an integer variable to hold the total bytes of memory the object uses. This should be the same as the product of the shape array multiplied by the number of bytes per item of memory. writeable address of an integer variable to hold whether or not the memory is writeable. If this is NULL, then you must assume the memory is read-only. format address of a format-string (following extended struct syntax) indicating what is in each element of of memory. The number of elements is len / itemsize, where itemsize is the number of bytes implied by the format. NULL if not needed in which case format is "B" for unsigned bytes. The memory for this string must not be freed by the consumer --- it is managed by the exporter. ndims address of a variable storing the number of dimensions or NULL if not needed. If shape and/or strides are given then this must be non NULL. If this variable is not provided then it is assumed that ``*ndims == 1``. shape address of a ``Py_ssize_t*`` variable that will be filled with a pointer to an array of ``Py_ssize_t`` of length ``*ndims`` indicating the shape of the memory as an N-D array. Ignored if this is NULL. Note that ``((*shape)[0] * ... * (*shape)[ndims-1])*itemsize = len``. If this variable is not provided then it is assumed that ``(*shape[0]) == len / itemsize``. strides address of a ``Py_ssize_t*`` variable that will be filled with a pointer to an array of ``Py_ssize_t`` of length ``*ndims`` indicating the number of bytes to skip to get to the next element in each dimension. If this is NULL, then the memory is assumed to be C-style contigous with the last dimension varying the fastest. An error should be raised if this is not accurate and strides are not requested. This variable may be set to NULL (with no error set) if memory is actually C-style contiguous. segments address to array-of-pointers-style array model. Only one of strides or segments can be used (the other one must be NULL). If the object does not support this kind of memory model and it is requested, then an error should be raised and *segments set to NULL. The segments variable should be recast to a pointer-to-a-pointer-to-a-pointer-...-to-a-pointer depending on the output of ndims. Thus, if ndims is 2, segments should be cast to (<type> ***) so that (*segments)[i][j] refers to the (i,j)th element of the array. If ndims is 3, segments should be cast to (<type> ****) so that (*segments)[i][j][k] refers to the (i,j,k)th element of the array. The view object should be used in the other API call and does not need to be decref'd. It should be "released" if the interface exporter provides the bf_releasebuffer function. Otherwise, it may be discared. The view object is exporter-specific. ``typedef int (*releasebufferproc)(PyObject *view)`` This function is called (if defined by the exporting object) when a view of memory previously acquired from the object is no longer needed. It is up to the exporter of the API to make sure all views have been released before re-allocating any previously shared memory. It is up to consumers of the API to call this function on the object whose view is obtained when it is no longer needed. Any format string, shape array or strides array returned through the interface should also not be referenced after the releasebuffer call is made. A -1 is returned on error and 0 on success. Both of these routines are optional for a type object New C-API calls are proposed ============================ :: int PyObject_CheckBuffer(PyObject *obj) Return 1 if the getbuffer function is available otherwise 0. :: PyObject * PyObject_GetBuffer(PyObject *obj, void **buf, Py_ssize_t *len, int *writeable, char **format, int *ndims, Py_ssize_t **shape, Py_ssize_t **strides, void **segments) Get the buffer and optional information variables about the buffer. Return an object-specific view object (which may be simply a borrowed reference to the object itself). :: int PyObject_ReleaseBuffer(PyObject *view) Call this function to tell obj that you are done with your "view" This doesn't do anything if the object doesn't implement a release function. Only call this after a previous PyObject_GetBuffer has succeeded and when you will not be needing or referring to the memory (or the format, shape, and strides memory used in the view -- if you will use these for a longer period of time make copies). Returns -1 on error. :: int PyObject_SizeFromFormat(char *) Return the implied itemsize of the data-format area from a struct-style description. :: int PyObject_GetContiguous(PyObject *obj, void **buf, Py_ssize_t *len) Return a contiguous chunk of memory representing the buffer. If a copy is made then return 1. If no copy was needed return 0. If an error occurred in probing the buffer interface, then return -1. The contiguous chunk of memory is pointed to by ``*buf`` and the length of that memory is ``*len``. The buffer is C-style contiguous meaning the last dimension varies the fastest. :: int PyObject_CopyToObject(PyObject *obj, void *buf, Py_ssize_t len) Copy ``len`` bytes of data pointed to by the contiguous chunk of memory pointed to by ``buf`` into the buffer exported by obj. Return 0 on success and return -1 and raise an error on failure. If the object does not have a writeable buffer, then an error is raised. The data is copied into an array in C-style contiguous fashion meaning the last variable varies the fastest. The last two C-API calls allow a standard way of getting data in and out of Python objects no matter how it is actually stored. These calls use the buffer interface to perform their work. Additions to the struct string-syntax ===================================== The struct string-syntax is missing some characters to fully implement data-format descriptions already available elsewhere (in ctypes and NumPy for example). Here are the proposed additions: ================ =========== Character Description ================ =========== 't' bit (number before states how many bits) '?' platform _Bool type 'g' long double 'Z' complex (whatever the next specifier is) 'c' ucs-1 (latin-1) encoding 'u' ucs-2 'w' ucs-4 'O' pointer to Python Object 'T{}' structure (detailed layout inside {}) '(k1,k2,...,kn)' multi-dimensional array of whatever follows ':name:' optional name of the preceeding element '&' specific pointer (prefix before another charater) 'X{}' pointer to a function (optional function signature inside {}) ' ' ignored (allow better readability) ================ =========== The struct module will be changed to understand these as well and return appropriate Python objects on unpacking. Un-packing a long-double will return a decimal object. Unpacking 'u' or 'w' will return Python unicode. Unpacking a multi-dimensional array will return a list of lists. Un-packing a pointer will return a ctypes pointer object. Un-packing a bit will return a Python Bool. Spaces in the struct-string syntax will be ignored. Unpacking a named-object will return a Python class with attributes having those names. Endian-specification ('=','>','<') is also allowed inside the string so that it can change if needed. The previously-specified endian string is enforce until changed. The default endian is '='. According to the struct-module, a number can preceed a character code to specify how many of that type there are. The (k1,k2,...,kn) extension also allows specifying if the data is supposed to be viewed as a (C-style contiguous, last-dimension varies the fastest) multi-dimensional array of a particular format. Functions should be added to ctypes to create a ctypes object from a struct description, and add long-double, and ucs-2 to ctypes. Examples of Data-Format Descriptions ==================================== Here are some examples of C-structures and how they would be represented using the struct-style syntax: float 'f' complex double 'Zd' RGB Pixel data 'BBB' or 'B:r: B:g: B:b:' Mixed endian (weird but possible) '>i:big: <i:little:' Nested structure :: struct { int ival; struct { unsigned short sval; unsigned char bval; unsigned char cval; } sub; } 'i:ival: T{H:sval: B:bval: B:cval:}:sub:' Nested array :: struct { int ival; double data[16*4]; } 'i:ival: (16,4)d:data:' Code to be affected =================== All objects and modules in Python that export or consume the old buffer interface will be modified. Here is a partial list. * buffer object * bytes object * string object * array module * struct module * mmap module * ctypes module Anything else using the buffer API Issues and Details ================== The proposed locking mechanism relies entirely on the objects implementing the buffer interface to do their own thing. Ideally an object that implements the buffer interface and can re-allocate memory, should store in its structure at least a number indicating how many views are extant. If there are still un-released views to a memory location, then any subsequent reallocation should fail and raise an error. The sharing of strided memory is new and can be seen as a modification of the multiple-segment interface. It is motivated by NumPy. NumPy objects should be able to share their strided memory with code that understands how to manage strided memory because strided memory is very common when interfacing with compute libraries. Currently the struct module does not allow specification of nested structures. The modifications to struct requested allow for specifying nested structures as several ways of viewing memory areas (e.g. ctypes and NumPy) already allow this. Memory management of the format string and the shape and strides array is always the responsibility of the exporting object and can be shared between different views. If the consuming object needs to keep these memory areas longer than the view is held, then it must copy them to its own memory. Copyright ========= This PEP is placed in the public domain
_______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com