Emulating Sequences

In this example, we will emulate a buffer object (size fixed at creation time, elements are single character strings).
staticforward PyTypeObject MyBuffer_Type;
typedef struct {
  PyObject_HEAD
  int size;
  char *p;
} Buffer;
#define Buffer_Check(v) ((PyObject *)(v)->ob_type == &MyBuffer_Type)
#define Buffer_size(v)  ((Buffer *)(v)->size)
#define Buffer_data(v)  ((Buffer *)(v)->p)
The Buffer type contains a fixed length character array. Array subscripting works with a string of size one. Slicing works on strings and buffers, returning a new buffer of the size of the slice.

Registering Methods

Define a variable of type PySequenceMethods and include a pointer to it in the tp_as_sequence slot in the PyTypeObject definition of a new type.

PySequenceMethods counter_as_sequence = {
    Buffer_Len,       /* inquiry sq_length;             /* __len__ */
    Buffer_Concat,    /* binaryfunc sq_concat;          /* __add__ */
    Buffer_Repeat,    /* intargfunc sq_repeat;          /* __mul__ */
    Buffer_GetItem,   /* intargfunc sq_item;            /* __getitem__ */
    Buffer_GetSlice,  /* intintargfunc sq_slice;        /* __getslice__ */
    Buffer_SetItem,   /* intobjargproc sq_ass_item;     /* __setitem__ */
    Buffer_SetSlice,  /* intintobjargproc sq_ass_slice; /* __setslice__ */
};
For deletion of items, the object passed to functions in the sq_ass_item and sq_ass_slice slots is NULL.

If a slot does not contain a function, replace it with 0.

Getting the length

The length in this example is fixed, but could be determined at runtime.

int
Buffer_Len(self)
  PyObject *self;
  { return Buffer_size(self);
  }

Single Item access

The index passed is an integer which may be negative or positive. If the index is out-of-bounds, then adjust to the closest boundary.

PyObject *
Buffer_GetItem(self, index)
  PyObject *self;
  int index;
  { PyObject *result = NULL;
    int n = Buffer_size(self);

    if (index < 0) index += n;
    if (0 <= index < n)
      result = PyString_FromStringAndSize(Buffer_data(self)[i], 1);
    else
      PyErr_SetString(PyExc_IndexError, "buffer index out of range");
    return result;
  }

int
Buffer_SetItem(self, index, value)
  PyObject *self, *value;
  int index;
  { int n = Buffer_size(self);
    char *c;

    if (value == NULL) {
      PyErr_SetString(PyExc_TypeError,
        "object doesn't support item deletion");
      return -1;
    } else if (!PyString_Check(value)) {
      PyErr_SetString(PyExc_TypeError,
        "expecting string");
      return -1;
    }

    if (index < 0) index += n;
    if (0 <= index < n) {
      c = PyString_AsString(value);
      Buffer_data(self)[index] = *c; /* just one character */
      return 0;
    } else {
      PyErr_SetString(PyExc_IndexError, "buffer index out of range");
      return -1;
    }
  }
If the passed value is NULL for the setitem function, then delete the item from the sequence.

Slices

The indices passed are integers which may be negative or positive. If an index is out-of-bounds, then adjust to the closest boundary. If the lower index is greater than the higher index, return an empty sequence. Where int is the return type, return is -1 on error or 0 on success.

int
Buffer_SetSlice(self, ilow, ihigh, value)
  PyObject *self, *value;
  int ilow, ihigh;
  { int i, n, l;
    char *p, *q;

    p = Buffer_data(self);
    n = Buffer_size(self);
    if (value == NULL) {
      PyErr_SetString(PyExc_TypeError,
        "object doesn't support item deletion");
      return -1;
    } else if (PyString_Check(value)) {
      q = PyString_AsString(value);
      l = PyString_Size(value);
    } else if (Buffer_Check(value)) {
      q = Buffer_data(value);
      l = Buffer_size(value);
    } else {
      PyErr_SetString(PyExc_TypeError,
        "expecting string or buffer");
      return -1;
    }

    if (ilow < 0) ilow += n;
    if (ihigh < 0) ihigh += n;
    if (ilow >= n) ilow = n-1;
    if (ihigh > n) ihigh = n;

    if (ihigh - ilow > l) {
      PyErr_SetString(PyExc_ValueError,
        "string must be at least size of slice");
      return -1;
    }
    if (0 <= ilow < ihigh < n)
      for (i = ilow; i < ihigh; i++)
        p[i] = *q++;
    return 0;
  }

PyObject *
Buffer_GetSlice(self, ilow, ihigh)
  PyObject *self;
  int ilow, ihigh;
  { PyObject *temp, *result = NULL;
    int n = Buffer_size(self);

    if (ilow < 0) ilow += n;
    if (ihigh < 0) ihigh += n;
    if (ilow >= n) ilow = n-1;
    if (ihigh > n) ihigh = n;

    if (0 <= ilow < ihigh < n) {
      temp = PyString_FromStringAndSize(Buffer_data(self)+ilow, ihigh-ilow);
      result = Buffer_NEW(ihigh-ilow);
      if (Buffer_SetSlice(result, 0, ihigh-ilow, temp) != -1) {
        Py_DECREF(result);
        return (PyObject *)NULL;
      }
    } else {
      result = Buffer_NEW(0);
    }
    return result;
  }
If the passed value is NULL for the setslice function then delete the elements in the slice.

Sequence Arithmetic

These methods are not required, but are encouraged for proper sequences. If the integer given to the repeat function is negative, then return an empty sequence (or an exception).
PyObject *
Buffer_Concat(self, other)
  PyObject *self, *other;
  { PyObject *result = NULL;
    int sl, ol;

    sl = Buffer_size(self);
    if (Buffer_Check(other)) {
      ol = Buffer_size(other);
    } else if (PyString_Check(other)) {
      ol = PyString_Size(other);
    } else {
      PyErr_SetString(PyExc_TypeError,
        "expecting string");
      return result;
    }
    result = Buffer_NEW(sl+ol);
    if (Buffer_SetSlice(result, 0, sl, self) == -1 ||
        Buffer_SetSlice(result, sl, sl+ol, other) == -1) {
      Py_DECREF(result);
      return (PyObject *)NULL;
    }
    return result;
  }

PyObject *
Buffer_Repeat(self, count)
  PyObject *self;
  int count;
  { PyObject *result = NULL;
    int p, n = Buffer_size(self);

    if (count < 0) {
      result = Buffer_NEW(0);
    } else {
      result = Buffer_NEW(n * count);
      if (result != NULL)
        for (p = 0; count>=0; count--, p+=n)
          if (Buffer_SetSlice(result, p, p+n, other) == -1) {
            Py_DECREF(result);
            return (PyObject *)NULL;
          }
    }
    return result;
  }

Suggested methods

Copyright (C) 1999 Michael P. Reilly, All rights reserved.
Written by Michael P. Reilly.