Attached is my revised PEP for the buffer protocol after incorporating suggestions from Greg Ewing. It is as simple as I can make it and still share what I think needs to be sharable. Suggestions are welcome. I will provide and maintain code to implement the PEP when the basic idea of the PEP is accepted.

Thanks,

-Travis


PEP: <unassigned>
Title: Revising the buffer protocol
Version: $Revision: $
Last-Modified: $Date:  $
Author: Travis Oliphant <[EMAIL PROTECTED]>
Status: Draft
Type: Standards Track
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 are eliminated and
   additional function pointers are 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's 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.)

    There are two widely used libraries that use the concept of
    discontiguous memory: PIL and NumPy.  Their view of discontiguous
    arrays is a bit different, though.  NumPy uses the notion of
    constant striding in each dimension as its basic concept of an
    array. In this way a simple sub-region of a larger array can be
    described without copying the data.  Strided memory is also a common
    way to describe data in many computing libraries (such as the BLAS
    and LAPACK).

    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.  This allows the
    image to not be loaded entirely into memory but still managed
    abstractly as if it were. I believe, the PIL is where the idea of
    multiple buffer segments in the original buffer interface came
    from, I believe.

    The buffer interface should allow discontiguous memory areas to
    share standard striding information.  However, consumers that do
    not want to deal with strided memory should also be able to
    request a contiguous segment easily.    


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 memory_view object that is returned from the 
     buffer interface getbuffer call.  This memory_view object
     contains
   * Add a new function to allow the interface to describe what is in
     memory (unifying what is currently done now in struct and
     array)

   * Add a new function to allow the protocol to share shape and 
     stride information

   * 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)
 
      Return a pointer to memory in *buf and the length of that memory
      buffer (in bytes) in *len.  The next arguments are optional.  
      NULL is returned on failure.   On success an oject-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. 

      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 == "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. 
      stride    -- 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 when called if memory is C-style
                     contiguous. 
                
      This 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.

    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)

      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. 


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 c-types long_double.  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.

   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 should keep at least
   a number indicating how many releases are extant.  If there are 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.  It seems like specifying a nested structure should be
   specified 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-3000 mailing list
Python-3000@python.org
http://mail.python.org/mailman/listinfo/python-3000
Unsubscribe: 
http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com

Reply via email to