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

Reply via email to