Create Extension Types

  1. A Dive-in Example
  2. Defining A New Extension Type
  3. Adding Instance Members to Types
  4. Adding Class Members to Types
  5. Adding Methods to Types
  6. Adding Special Methods
  7. Adding Properties
  8. Generate and Test the Example
  9. Create Base Extension Types
  10. Create Numeric Types
  11. Create Mapping Types
  12. Create Sequence Types
  13. Create Iterator Types

A Dive-in Example

In the example below, the module only has a class extension:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
"""My 3rd module with expy-cxpy."""
#File name: module3.py
from expy import * 
expymodule("module3") #BEGIN:module3
@prologue
def macros():
   return """
#define VERSION "0.1.0"
"""

class mate(public):
   """mate: a class for a buddy."""
   _age = ifield(int, acc=private, doc='the age')
   name = ifield(str, flag='RO', doc='the name')
   version = cfield(raw_type(str, 'VERSION')) #class field/member

   def __init__(me, name=str, age = int):
      return """{
   me->_age = age; me->name = 0;
   mate_rename(me, name);
   return me->name == 0; //failed?
}"""

   @imethod(void)
   def rename(me, name=str):
      """change the name."""
      return """{
   if(me->name) free(me->name);
   int nn = strlen(name)+1;
   me->name = malloc(sizeof(char)*nn);
   if(me->name) strcpy(me->name, name);
   else PyErr_SetString(PyExc_RuntimeError, 
             "Out of Memory!");
}"""

   def get_age(me, closure=raw_type('void*')):
      return """{
   return Py_BuildValue("l", me->_age);
}"""

   def set_age(me, v, closure=raw_type('void*')):
      return """{
   if(!PyInt_Check(v)){ 
      PyErr_SetString(PyExc_TypeError, "Must be an Integer");
      return 1; //failed
   }
   int age = PyInt_AsLong(v);
   if (age <= 0){
      PyErr_SetString(PyExc_ValueError, "Must be Positive");
      return 1; //failed
   }
   me->_age = age;
   return 0; //success
}"""

   age = property(get_age, set_age, doc="the age.")

expymodule("module3") #END:module3

The explanation of this example follows.

Defining A New Extension Type

Line 6 start the definition of your extension type: the main thing you should notice is that it extends public, which is a class defined in the expy module. This basically says that this class should be totally visible in Python.

Adding Instance Members to Types

Line 8-9 declares two instance members, by using the ifield (i.e.: instance field) class provided in the expy module. You provide the type, the access (such as public or private), the document string, and you can also provide the flag (must be a string), which corresponds to the flags field of the PyMemberDef structure in Python C API. In Line 8, the _age member is of ‘int’ type, and it is a private member. In Line 9, the name member is of ‘str’ type (which in C it corresponds to ‘char*’), and is ‘readonly’, as indicated by the “RO” flag. One more good thing:

The order in which these fields appear will be
preserved in the generated object structure.

Adding Class Members to Types

Class members/attributes of extension types are read only, thus they are ideal for providing constants/data to users. Line 10 defines a class member/field, which is the version of the class. Instead of using an ‘ifield’ constructor, you use a ‘cfield’ (class field) constructor, which takes only one parameter, namely, the value you want to store. This parameter is specified in the same way as default values to your function arguments. This class field is accessed by mate.version, once you have built and imported the module.

Adding Methods to Types

Lines 19-29 defines a method named “rename” for the extension type. In Line 19, we use the decorator imethod to declare that this is an “instance method”, whose return type is ‘void’. The first argument will be the instance, the next argument is the new name, of type ‘str’ (which translated in C is ‘char*’). It is very similar to how we defined functions for modules. The name mangling is done by appending the class name to the function name, connected by an underscore ‘_’.

Adding Special Methods

Lines 12-17 defines a special method “__init__”, which initializes an instance. Those special methods does not need any decoration, they are automatically detected, because their return type, access type, etc, are known in advance. Note that for special methods, a code block that starts and ends with a curly brace must be provided, never return an expression. Other special methods that are recognized:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
spec_dargs={
'__init__':{'cat':'imethod', 'ret':int, 'acc':public},
#def __new__(cls, args, kwds):
'__new__':{'cat':'cmethod', 'ret':object, 'acc':public, 'nowrap':True},
#def __del__(me): ??
'__del__':{'cat':'imethod', 'ret':void, 'acc':public, 'nowrap':True},
#def __call__(cls, args, kwds):
'__call__':{'cat':'imethod', 'ret':object, 'acc':public, 'nowrap':True},
#def __cmp__(me, u=lambda:mytype): or def __cmp__(me,u): (?)
'__cmp__':{'cat':'imethod', 'ret':int, 'acc':public, 'nowrap':True},
#def __getattr__(me,name=str):
'__getattr__':{'cat':'imethod', 'ret':object, 'acc':public, 'nowrap':True},
#def __setattr__(me, name=str, attr=object):
'__setattr__':{'cat':'imethod', 'ret':int, 'acc':public, 'nowrap':True},
#def __getattro__(me, name):
'__getattro__':{'cat':'imethod', 'ret':object, 'acc':public, 'nowrap':True},
#def __setattro__(me, name, attr):
'__setattro__':{'cat':'imethod', 'ret':int, 'acc':public, 'nowrap':True},
#def __hash__(me):
'__hash__':{'cat':'imethod', 'ret':long, 'acc':public, 'nowrap':True},
#def __print__(me, f=raw_type('FILE*'), flags=int):
'__print__':{'cat':'imethod', 'ret':int, 'acc':public, 'nowrap':True},
#def __repr__(me):
'__repr__':{'cat':'imethod', 'ret':object, 'acc':public, 'nowrap':True},
#def __str__(me):
'__str__':{'cat':'imethod', 'ret':object, 'acc':public, 'nowrap':True},
#def __iter__(me):
'__iter__':{'cat':'imethod', 'ret':object, 'acc':public, 'nowrap':True},
#def __next__(me):
'__next__':{'cat':'imethod', 'ret':object, 'acc':public, 'nowrap':True},
}

You should refer to the Python C API to learn the signature of those special methods, and how to implement them.

Adding Properties

Lines 31-51 defines a property named ‘age’. Tow functions ‘get_age’ and ‘set_age’ are defined, without decoration. They must conform to that signature so that the generated C function is a well defined getter and setter function for a property. The main feature provided here is to throw an exception if the age argument is of the wrong type or value.

Generate and Test the Example

Now we use the following setup code to generate and compile this module.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# File: module3_setup.py
from distutils.core import setup, Extension
import sys, os, os.path, re
#To find the 'expy' and 'cxpy' modules
sys.path.insert(1, '../src')
import cxpy, module3 as modu
cxpy.gencode(modu) #generate the C code
# Now, finally, define that module!
modext = Extension( modu.__name__, 
   sources = [modu.__name__+'.c'])
setup(name = modu.__name__, version = '0.1.0',
      description = 'My third module.',
      ext_modules = [modext])

And here is the generated C code:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#include <Python.h>
#include "structmember.h"

#define VERSION "0.1.0"

/*--- class mate ---*/

typedef struct{
  PyObject_HEAD
  int _age;
  char* name;
} mate;

static PyTypeObject mateType;

void mate_rename(mate* me, const char* name);
static PyObject* mate_rename_wrap(
   mate* me,
   PyObject* _args_
);
static int mate_set_age(mate* me, PyObject* v, void* closure);
static PyObject* mate_get_age(mate* me, void* closure);
int mate___init__(mate* me, const char* name, int age);
static PyObject* mate___init___wrap(
   mate* me,
   PyObject* _args_
);
#define mate_Check(op) PyObject_TypeCheck(op, &mateType)

/**======== MODULE IMPLEMENTATION ========**/

/*--- class mate ---*/
void mate_rename(mate* me, const char* name){
   if(me->name) free(me->name);
   int nn = strlen(name)+1;
   me->name = malloc(sizeof(char)*nn);
   if(me->name) strcpy(me->name, name);
   else PyErr_SetString(PyExc_RuntimeError, 
             "Out of Memory!");
}
static PyObject* mate_rename_wrap(
   mate* me,
   PyObject* _args_
){
    const char* name;
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "s"
       , &name)) return 0;

   mate_rename(me, name);
    return Py_BuildValue("");
}

static int mate_set_age(mate* me, PyObject* v, void* closure){
   if(!PyInt_Check(v)){ 
      PyErr_SetString(PyExc_TypeError, "Must be an Integer");
      return 1; //failed
   }
   int age = PyInt_AsLong(v);
   if (age <= 0){
      PyErr_SetString(PyExc_ValueError, "Must be Positive");
      return 1; //failed
   }
   me->_age = age;
   return 0; //success
}
static PyObject* mate_get_age(mate* me, void* closure){
   return Py_BuildValue("l", me->_age);
}
int mate___init__(mate* me, const char* name, int age){
   me->_age = age; me->name = 0;
   mate_rename(me, name);
   return me->name == 0; //failed?
}
static PyObject* mate___init___wrap(
   mate* me,
   PyObject* _args_
){
    const char* name;
    int age;
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "si"
       , &name, &age)) return 0;

    int cxpyret = mate___init__(me, name, age);
    return Py_BuildValue("i", cxpyret);
}

static struct PyMethodDef mateMeths[] = {
{"rename", (PyCFunction)mate_rename_wrap, METH_VARARGS,
"change the name."},
{NULL}           /* sentinel */ };

static PyMemberDef mateFields[] = {
{"name", T_STRING, offsetof(mate, name), RO,
"the name"},
{NULL}  /* Sentinel */
};

/**Type mate is not numeric.**/

/**Type mate is not mapping.**/

/**Type mate is not sequence.**/


static PyGetSetDef mateProps[] = {
{"age", (getter)mate_get_age, (setter)mate_set_age,
      "the age.", NULL},
    {NULL}  /* Sentinel */
};


static PyTypeObject mateType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "module3.mate",       /*tp_name*/
    sizeof(mate),          /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)0,       /*tp_dealloc*/
    (printfunc)0,      /*tp_print*/
    (getattrfunc)0,  /*tp_getattr*/
    (setattrfunc)0,  /*tp_setattr*/
    (cmpfunc)0,          /*tp_compare*/
    (reprfunc)0,        /*tp_repr*/
    0,                  /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    (hashfunc)0,        /*tp_hash */
    (ternaryfunc)0,     /*tp_call*/
    (reprfunc)0,         /*tp_str*/
    (getattrofunc)0,/*tp_getattro*/
    (setattrofunc)0,/*tp_setattro*/
    0,                  /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,                  /*tp_flags*/
    "mate: a class for a buddy.",                  /* tp_doc */
    (traverseproc)0,	       /* tp_traverse */
    (inquiry)0,		       /* tp_clear */
    (richcmpfunc)0,	 /* tp_richcompare */
    0,		               /* tp_weaklistoffset */
    (getiterfunc)0,        /* tp_iter */
    (iternextfunc)0,	  /* tp_iternext */
    mateMeths,                  /* tp_methods */
    mateFields,                  /* tp_members */
    mateProps,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    (descrgetfunc)0,       /* tp_descr_get */
    (descrsetfunc)0,       /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)mate___init___wrap,        /* tp_init */
    0,                         /* tp_alloc */
    0,                   /* tp_new */
};/*http://docs.python.org/c-api/typeobj.html*/

static struct PyMethodDef module3Funcs[] = {
{NULL}           /* sentinel */ };


PyMODINIT_FUNC 
initmodule3(void) {
  PyObject *mod;
  
    //Initialize the type table for "mate".
    if(mateType.tp_new == 0) 
         mateType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&mateType) < 0) return;

  mod = Py_InitModule3("module3", module3Funcs, 
     "My 3rd module with expy-cxpy.");
  if (mod==0) return;
  
    Py_INCREF(&mateType);
    PyModule_AddObject(mod, "mate", (PyObject *)&mateType);
    /* class fields for class mate: */

  PyDict_SetItemString(mateType.tp_dict, "version", 
     Py_BuildValue("s",VERSION));
  /*End class mate.*/

  /* Global Members */

}

Start a Python session and test what we have got here:

ylan@montop:~/expy/Doc$ python module3_setup.py build
ylan@montop:~/expy/Doc$ cd build/lib.linux-i686-2.5/
ylan@montop:~/expy/Doc/build/lib.linux-i686-2.5$ python
Python 2.5.2 (r252:60911, Jul 31 2008, 17:28:52)
[GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from module3 import mate
>>> x = mate('John', 23)
>>> x.name
'John'
>>> x.age
23
>>> x.age = -23
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Must be Positive
>>> x.age = x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Must be an Integer
>>> x.age
23
>>> x.rename('Job')
>>> x.name
'Job'

Create Base Extension Types

If you have implemented your methods carefully, you can declare your extension types as a base type, so that somebody can extend it in pure Python. To declare that your type can serve as a base type, you simply do this:

class mybase(public, basetype):
   """this extension type can be a base type."""
   def ameth(me, you):
      ...

For more information on how to implement a base type, please refer to Python C API.

Create Numeric Types

Python allows one to create types/classes that can act like numbers, by implementing the numeric protocol. You can create a numeric extension type in similar ways as you create a pure python numeric type with expy-cxpy. There are, however, two distinct categories of numeric extension types in Python C API. Category 1: the special numeric type, whose numeric operations are only defined between an object of the defining type and an object of the same type; Category 2: the generic numeric type, whose numeric operations are defined between an object of the defining type and an object of an arbitrary type (well, sort of). When you implement your numeric type, you must decide which one of the two categories your new type will belong to.

The following example is for Category 1. A new extension type ‘counter’ is created, it has an integer number, and a name. We only defined the __add__ operator, when two counters added together, not only their numbers are added, but also their names are merged.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"""Numeric extension type (Cat 1) with expy-cxpy."""
#File name: module4.py
from expy import * 
expymodule(__name__) 

class counter(public):
   """counter: a simple numeric class."""
   num = ifield(int, doc='the counter')
   name = ifield(str, flag='RO', doc='the name')

   def __init__(me, name=str, num=0):
      return """{
   me->num = num;
   num = strlen(name)+1; 
   me->name = malloc(sizeof(char)*num);
   if (!me->name) return 1; //fail
   strcpy(me->name, name);
   return 0; //success!
}"""

   def __add__(me, u=lambda:counter):
      """add counters, and merge names."""
      return """{
   counter* sum = (counter*) me->ob_type->tp_alloc(me->ob_type,0);
   sum->num = me->num + u->num;
   int lm = strlen(me->name);
   sum->name = malloc(sizeof(char)*(lm+strlen(u->name)+2));
   if(!sum->name){ 
      Py_DECREF(sum);
      return 0; //no memory
   }
   strcpy(sum->name, me->name);
   strcpy(sum->name+lm, "&");
   strcpy(sum->name+lm+1, u->name);
   return (PyObject*)sum;
}"""

expymodule(__name__) 

To enforce that the second argument to __add__ function must be of the same ‘counter’ type, we use ‘lambda:counter’ to indicate its type. It might be more appealing to simply use something like ‘__add__(me, u=counter)’, but since ‘counter’ is not defined yet, Python compiler would complain about this and abort, so the ‘lambda’ function is used here to delay the evaluation till code generation. Here is the setup script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# File: module4_setup.py
from distutils.core import setup, Extension
import sys, os, os.path, re
#To find the 'expy' and 'cxpy' modules
sys.path.insert(1, '../src')
import cxpy, module4 as modu
cxpy.gencode(modu) #generate the C code
# Now, finally, define that module!
modext = Extension( modu.__name__, 
   sources = [modu.__name__+'.c'])
setup(name = modu.__name__, version = '0.1.0',
      description = 'My third module.',
      ext_modules = [modext])

And the generated C code is also included for your information:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#include <Python.h>
#include "structmember.h"

/*--- class counter ---*/

typedef struct{
  PyObject_HEAD
  int num;
  char* name;
} counter;

static PyTypeObject counterType;

int counter___init__(counter* me, const char* name, int num);
static PyObject* counter___init___wrap(
   counter* me,
   PyObject* _args_
);
static PyObject* counter___add__(counter* me, counter* u);
#define counter_Check(op) PyObject_TypeCheck(op, &counterType)

/**======== MODULE IMPLEMENTATION ========**/

/*--- class counter ---*/
int counter___init__(counter* me, const char* name, int num){
   me->num = num;
   num = strlen(name)+1; 
   me->name = malloc(sizeof(char)*num);
   if (!me->name) return 1; //fail
   strcpy(me->name, name);
   return 0; //success!
}
static PyObject* counter___init___wrap(
   counter* me,
   PyObject* _args_
){
    const char* name;
    int num=0;
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "s|i"
       , &name, &num)) return 0;

    int cxpyret = counter___init__(me, name, num);
    return Py_BuildValue("i", cxpyret);
}

static PyObject* counter___add__(counter* me, counter* u){
   counter* sum = (counter*) me->ob_type->tp_alloc(me->ob_type,0);
   sum->num = me->num + u->num;
   int lm = strlen(me->name);
   sum->name = malloc(sizeof(char)*(lm+strlen(u->name)+2));
   if(!sum->name){ 
      Py_DECREF(sum);
      return 0; //no memory
   }
   strcpy(sum->name, me->name);
   strcpy(sum->name+lm, "&");
   strcpy(sum->name+lm+1, u->name);
   return (PyObject*)sum;
}
static struct PyMethodDef counterMeths[] = {
{NULL}           /* sentinel */ };

static PyMemberDef counterFields[] = {
{"num", T_INT, offsetof(counter, num), 0,
"the counter"},
{"name", T_STRING, offsetof(counter, name), RO,
"the name"},
{NULL}  /* Sentinel */
};


static PyNumberMethods counterAsNum = {
        (binaryfunc)counter___add__,       /*nb_add*/
        (binaryfunc)0,       /*nb_subtract*/
        (binaryfunc)0,       /*nb_multiply*/
        (binaryfunc)0,       /*nb_divide*/
        (binaryfunc)0,       /*nb_remainder*/
        (binaryfunc)0,    /*nb_divmod*/
        (ternaryfunc)0,      /*nb_power*/
        (unaryfunc)0,        /*nb_negative*/
        (unaryfunc)0,        /*nb_positive*/
        (unaryfunc)0,        /*nb_absolute*/
        (inquiry)0,      /*nb_nonzero*/
        (unaryfunc)0,     /*nb_invert*/
        (binaryfunc)0,    /*nb_lshift*/
        (binaryfunc)0,    /*nb_rshift*/
        (binaryfunc)0,       /*nb_and*/
        (binaryfunc)0,       /*nb_xor*/
        (binaryfunc)0,        /*nb_or*/
        (coercion)0,      /*nb_coerce*/
        (unaryfunc)0,        /*nb_int*/
        (unaryfunc)0,       /*nb_long*/
        (unaryfunc)0,      /*nb_float*/
        (unaryfunc)0,        /*nb_oct*/
        (unaryfunc)0,        /*nb_hex*/

     /* Added in release 2.0 */
     (binaryfunc)0,        /*nb_inplace_add*/
     (binaryfunc)0,        /* nb_inplace_subtract */
     (binaryfunc)0,        /* nb_inplace_multiply */
     (binaryfunc)0,        /* nb_inplace_divide */
     (binaryfunc)0,        /* nb_inplace_remainder */
     (ternaryfunc)0,        /*nb_inplace_power */
     (binaryfunc)0,        /* nb_inplace_lshift */
     (binaryfunc)0,        /* nb_inplace_rshift */
     (binaryfunc)0,        /* nb_inplace_and */
     (binaryfunc)0,        /* nb_inplace_xor */
     (binaryfunc)0,        /* nb_inplace_or */

     /* Added in release 2.2 */
     (binaryfunc)0,        /* nb_floor_divide */
     (binaryfunc)0,        /* nb_true_divide */
     (binaryfunc)0,        /* nb_inplace_floor_divide */
     (binaryfunc)0,        /* nb_inplace_true_divide */

     /* Added in release 2.5 */
     (unaryfunc)0,         /* nb_index */
};

/**Type counter is not mapping.**/

/**Type counter is not sequence.**/

/* NO PROPS: counter */


static PyTypeObject counterType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "module4.counter",       /*tp_name*/
    sizeof(counter),          /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)0,       /*tp_dealloc*/
    (printfunc)0,      /*tp_print*/
    (getattrfunc)0,  /*tp_getattr*/
    (setattrfunc)0,  /*tp_setattr*/
    (cmpfunc)0,          /*tp_compare*/
    (reprfunc)0,        /*tp_repr*/
    &counterAsNum,                  /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    (hashfunc)0,        /*tp_hash */
    (ternaryfunc)0,     /*tp_call*/
    (reprfunc)0,         /*tp_str*/
    (getattrofunc)0,/*tp_getattro*/
    (setattrofunc)0,/*tp_setattro*/
    0,                  /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,                  /*tp_flags*/
    "counter: a simple numeric class.",                  /* tp_doc */
    (traverseproc)0,	       /* tp_traverse */
    (inquiry)0,		       /* tp_clear */
    (richcmpfunc)0,	 /* tp_richcompare */
    0,		               /* tp_weaklistoffset */
    (getiterfunc)0,        /* tp_iter */
    (iternextfunc)0,	  /* tp_iternext */
    counterMeths,                  /* tp_methods */
    counterFields,                  /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    (descrgetfunc)0,       /* tp_descr_get */
    (descrsetfunc)0,       /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)counter___init___wrap,        /* tp_init */
    0,                         /* tp_alloc */
    0,                   /* tp_new */
};/*http://docs.python.org/c-api/typeobj.html*/

static struct PyMethodDef module4Funcs[] = {
{NULL}           /* sentinel */ };


PyMODINIT_FUNC 
initmodule4(void) {
  PyObject *mod;
  
    //Initialize the type table for "counter".
    if(counterType.tp_new == 0) 
         counterType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&counterType) < 0) return;

  mod = Py_InitModule3("module4", module4Funcs, 
     "Numeric extension type (Cat 1) with expy-cxpy.");
  if (mod==0) return;
  
    Py_INCREF(&counterType);
    PyModule_AddObject(mod, "counter", (PyObject *)&counterType);
    /* class fields for class counter: */
  /*End class counter.*/

  /* Global Members */

}

And a testing session is invoked to illustrate the result:

ylan@montop:~/expy/Doc/build/lib.linux-i686-2.5$ python
>>> from module4 import *
>>> a = counter('VA', 20)
>>> b = counter('MD', 14)
>>> c = a+b
>>> c.name, c.num
('VA&MD', 34)

Defining numeric extension of Category 2 is similar, only that the second operand will be of PyObject type, so the prototype would be something like this:

def __add__(me, u):
   """The C prototype would be:
counter___add__(counter* me, PyObject* u);"""

And before you work on the argument ‘u’, you need to do type check, generally speaking.

Create Mapping Types

You can also implement the mapping protocol for your extension types. Below is a simple example, where our mapping type only help access some static integer array.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
"""Mapping extension type with expy-cxpy."""
#File name: module5.py
from expy import * 
expymodule(__name__) 
@prologue
def myprolog():
   return """
#define MEMLEN 30 
static int mapst[MEMLEN]; 
"""
class statmap(public):
   """A Map type to access static array data."""

   def __maplen__(me):
      """The length of the map."""
      return """{ return MEMLEN; }"""

   def __mapget__(me, key):
      return """{
   if(!PyInt_Check(key)){ 
      PyErr_SetString(PyExc_TypeError, "Key must be an Integer");
      return NULL; //failed
   }
   int i = PyInt_AsLong(key);
   if (i<0) i += MEMLEN;
   if(i >= MEMLEN || i <0){
      PyErr_SetString(PyExc_ValueError, "Index out of Range");
      return NULL; //failed
   }
   return PyInt_FromLong(mapst[i]);
}"""

   def __mapset__(me, key, v):
      return """{
   if(!PyInt_Check(key)){ 
      PyErr_SetString(PyExc_TypeError, "Key must be an Integer");
      return 1; //failed
   }
   if(!PyInt_Check(v)){ 
      PyErr_SetString(PyExc_TypeError, "Must be an Integer");
      return 1; //failed
   }
   int i = PyInt_AsLong(key);
   if (i<0) i += MEMLEN;
   if(i >= MEMLEN || i <0){
      PyErr_SetString(PyExc_ValueError, "Index out of Range");
      return 1; //fail
   }
   mapst[i] = PyInt_AsLong(v);
   return 0; //good
}"""

expymodule(__name__) 

Note that the three special method names and the signature, which are different from the way you define a pure python mapping type. This is due to current cxpy implementation. This difference might disappear in the future. And here is the generated C code:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#include <Python.h>
#include "structmember.h"

#define MEMLEN 30 
static int mapst[MEMLEN]; 

/*--- class statmap ---*/

typedef struct{
  PyObject_HEAD
} statmap;

static PyTypeObject statmapType;

static int statmap___mapset__(statmap* me, PyObject* key, PyObject* v);
static PyObject* statmap___mapget__(statmap* me, PyObject* key);
static int statmap___maplen__(statmap* me);
#define statmap_Check(op) PyObject_TypeCheck(op, &statmapType)

/**======== MODULE IMPLEMENTATION ========**/

/*--- class statmap ---*/
static int statmap___mapset__(statmap* me, PyObject* key, PyObject* v){
   if(!PyInt_Check(key)){ 
      PyErr_SetString(PyExc_TypeError, "Key must be an Integer");
      return 1; //failed
   }
   if(!PyInt_Check(v)){ 
      PyErr_SetString(PyExc_TypeError, "Must be an Integer");
      return 1; //failed
   }
   int i = PyInt_AsLong(key);
   if (i<0) i += MEMLEN;
   if(i >= MEMLEN || i <0){
      PyErr_SetString(PyExc_ValueError, "Index out of Range");
      return 1; //fail
   }
   mapst[i] = PyInt_AsLong(v);
   return 0; //good
}
static PyObject* statmap___mapget__(statmap* me, PyObject* key){
   if(!PyInt_Check(key)){ 
      PyErr_SetString(PyExc_TypeError, "Key must be an Integer");
      return NULL; //failed
   }
   int i = PyInt_AsLong(key);
   if (i<0) i += MEMLEN;
   if(i >= MEMLEN || i <0){
      PyErr_SetString(PyExc_ValueError, "Index out of Range");
      return NULL; //failed
   }
   return PyInt_FromLong(mapst[i]);
}
static int statmap___maplen__(statmap* me){ return MEMLEN; }
static struct PyMethodDef statmapMeths[] = {
{NULL}           /* sentinel */ };

static PyMemberDef statmapFields[]={{0}};

/**Type statmap is not numeric.**/


static PyMappingMethods statmapAsMap = {
        (inquiry)statmap___maplen__,               /*mp_length*/
        (binaryfunc)statmap___mapget__,        /*mp_subscript*/
        (objobjargproc)statmap___mapset__,     /*mp_ass_subscript*/
};/*http://docs.python.org/c-api/typeobj.html#PyMappingMethods*/

/**Type statmap is not sequence.**/

/* NO PROPS: statmap */


static PyTypeObject statmapType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "module5.statmap",       /*tp_name*/
    sizeof(statmap),          /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)0,       /*tp_dealloc*/
    (printfunc)0,      /*tp_print*/
    (getattrfunc)0,  /*tp_getattr*/
    (setattrfunc)0,  /*tp_setattr*/
    (cmpfunc)0,          /*tp_compare*/
    (reprfunc)0,        /*tp_repr*/
    0,                  /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    &statmapAsMap,                         /*tp_as_mapping*/
    (hashfunc)0,        /*tp_hash */
    (ternaryfunc)0,     /*tp_call*/
    (reprfunc)0,         /*tp_str*/
    (getattrofunc)0,/*tp_getattro*/
    (setattrofunc)0,/*tp_setattro*/
    0,                  /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,                  /*tp_flags*/
    "A Map type to access static array data.",                  /* tp_doc */
    (traverseproc)0,	       /* tp_traverse */
    (inquiry)0,		       /* tp_clear */
    (richcmpfunc)0,	 /* tp_richcompare */
    0,		               /* tp_weaklistoffset */
    (getiterfunc)0,        /* tp_iter */
    (iternextfunc)0,	  /* tp_iternext */
    statmapMeths,                  /* tp_methods */
    statmapFields,                  /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    (descrgetfunc)0,       /* tp_descr_get */
    (descrsetfunc)0,       /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)0,        /* tp_init */
    0,                         /* tp_alloc */
    0,                   /* tp_new */
};/*http://docs.python.org/c-api/typeobj.html*/

static struct PyMethodDef module5Funcs[] = {
{NULL}           /* sentinel */ };


PyMODINIT_FUNC 
initmodule5(void) {
  PyObject *mod;
  
    //Initialize the type table for "statmap".
    if(statmapType.tp_new == 0) 
         statmapType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&statmapType) < 0) return;

  mod = Py_InitModule3("module5", module5Funcs, 
     "Mapping extension type with expy-cxpy.");
  if (mod==0) return;
  
    Py_INCREF(&statmapType);
    PyModule_AddObject(mod, "statmap", (PyObject *)&statmapType);
    /* class fields for class statmap: */
  /*End class statmap.*/

  /* Global Members */

}

The testing of this is left for the reader.

Create Sequence Types

To implement the sequence protocol, simply define those special methods for the sequence protocol.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""Sequence extension type with expy-cxpy."""
#File name: module6.py
from expy import * 
expymodule(__name__) 

class strbuf(public):
   """A Sequence type to simulate StringBuffer."""
   size = ifield(int)
   leng = ifield(int)
   buff = ifield(str, flag='RO')

   def __init__(me, size=int):
      return """{
   me->size = size;
   me->leng = 0;
   me->buff = malloc(sizeof(char)*(size+1));
   if(!me->buff) return 1;
   me->buff[me->leng] = 0;
   return 0;
}"""

   def __len__(me):
      """The length of the buffer."""
      return """{ return me->leng; }"""

   def __getitem__(me, i=int):
      return """{
   if (i<0) i += me->leng;
   if(i >= me->leng || i<0){
      PyErr_SetString(PyExc_ValueError, "Index out of Range");
      return 0;
   }
   return PyString_FromStringAndSize(me->buff+i,1);
}"""

   def __setitem__(me, i=int, v=object):
      return """{
   if (i<0) i += me->leng;
   if(i >= me->leng || i<0){
      PyErr_SetString(PyExc_ValueError, "Index out of Range");
      return 1; //failed
   }
   if(!PyString_Check(v) || PyString_Size(v)!=1){ 
      PyErr_SetString(PyExc_TypeError, "Must be a String of size 1.");
      return 1; //failed
   }
   me->buff[i] = PyString_AsString(v)[0];
   return 0;
}"""

   @imethod(int)
   def enlarge(me, size=0):
      """enlarge to the given size, or double the capacity,
whichever is the bigger."""
      return """{
   if(size < me->size*2 +1) 
      size = 1 + me->size * 2;
   char* buff = malloc(sizeof(char)*size);
   if(!buff) return -1; //fail
   strcpy(buff, me->buff);
   free(me->buff);
   me->buff = buff;
   me->size = size;
   return size;
}"""

   def __concat__(me, u):
      return """{
   if(!PyString_Check(u) && !strbuf_Check(u))
     return NULL; //failed
   const char* buf = NULL; 
   int len = me->leng;
   if(PyString_Check(u)){ 
      buf = PyString_AsString(u);
      len += PyString_Size(u);
   }else if(strbuf_Check(u)){
      buf= ((strbuf*)u)->buff;
      len += ((strbuf*)u)->leng;
   }
   strbuf* sb = (strbuf*)me->ob_type->tp_alloc(me->ob_type, 0);
   if(!sb) return NULL;
   sb->size = sb->leng = len;
   sb->buff = malloc(sizeof(char)*(len+1));
   if(!sb->buff){ Py_DECREF(sb); return NULL; }
   strcpy(sb->buff, me->buff);
   strcpy(sb->buff + me->leng, buf);
   return (PyObject*)sb;
}"""

   def __iconcat__(me, u):
      return """{
   if(!PyString_Check(u) && !strbuf_Check(u))
     return NULL; //failed
   int len = me->leng;
   const char* buf = NULL;
   if(PyString_Check(u)){ 
      buf = PyString_AsString(u);
      len += PyString_Size(u);
   }else if(strbuf_Check(u)){
      buf = ((strbuf*)u)->buff;
      len += ((strbuf*)u)->leng;
   }
   if(len > me->size) 
      if(strbuf_enlarge(me, len)<0)
         return NULL; //failed
   strcpy(me->buff+me->leng, buf);
   me->leng  = len;
   Py_INCREF(me); //New Reference!
   return (PyObject*) me;
}"""

   def __repeat__(me, t=int):
      return """{
   if (t<=0) return NULL;
   int len = me->leng * t;
   strbuf* sb = (strbuf*)me->ob_type->tp_alloc(me->ob_type, 0);
   if(!sb) return NULL;
   sb->size = sb->leng = len;
   sb->buff = malloc(sizeof(char)*(len+1));
   if(!sb->buff){ Py_DECREF(sb); return NULL; }
   char * buff = sb->buff;
   for(; t > 0; t--){ 
      strcpy(buff, me->buff);
      buff += me->leng;
   }
   return (PyObject*) sb;
}"""

   def __irepeat__(me, t=int):
      return """{
   if(t<=0) return NULL;
   int len = me->leng * t;
   if(len > me->size) 
      if(strbuf_enlarge(me, len)<0)
         return NULL; //failed
   char * buff = me->buff;
   for(; t > 0; t--){ 
      strncpy(buff, me->buff, me->leng);
      buff += me->leng;
   }
   me->leng = len;
   me->buff[len] = 0;
   Py_INCREF(me);
   return (PyObject*) me;
}"""

expymodule(__name__) 

Again notice the choice of the special method names __concat__ and __repeat__, which are not standard in pure python, this is again due to the current cxpy implementation. The generated C code:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#include <Python.h>
#include "structmember.h"

/*--- class strbuf ---*/

typedef struct{
  PyObject_HEAD
  int size;
  int leng;
  char* buff;
} strbuf;

static PyTypeObject strbufType;

int strbuf_enlarge(strbuf* me, int size);
static PyObject* strbuf_enlarge_wrap(
   strbuf* me,
   PyObject* _args_
);
static PyObject* strbuf___concat__(strbuf* me, PyObject* u);
static PyObject* strbuf___getitem__(strbuf* me, int i);
static int strbuf___setitem__(strbuf* me, int i, PyObject* v);
static PyObject* strbuf___irepeat__(strbuf* me, int t);
int strbuf___init__(strbuf* me, int size);
static PyObject* strbuf___init___wrap(
   strbuf* me,
   PyObject* _args_
);
static PyObject* strbuf___repeat__(strbuf* me, int t);
static PyObject* strbuf___iconcat__(strbuf* me, PyObject* u);
static int strbuf___len__(strbuf* me);
#define strbuf_Check(op) PyObject_TypeCheck(op, &strbufType)

/**======== MODULE IMPLEMENTATION ========**/

/*--- class strbuf ---*/
int strbuf_enlarge(strbuf* me, int size){
   if(size < me->size*2 +1) 
      size = 1 + me->size * 2;
   char* buff = malloc(sizeof(char)*size);
   if(!buff) return -1; //fail
   strcpy(buff, me->buff);
   free(me->buff);
   me->buff = buff;
   me->size = size;
   return size;
}
static PyObject* strbuf_enlarge_wrap(
   strbuf* me,
   PyObject* _args_
){
    int size=0;
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "|i"
       , &size)) return 0;

    int cxpyret = strbuf_enlarge(me, size);
    return Py_BuildValue("i", cxpyret);
}

static PyObject* strbuf___concat__(strbuf* me, PyObject* u){
   if(!PyString_Check(u) && !strbuf_Check(u))
     return NULL; //failed
   const char* buf = NULL; 
   int len = me->leng;
   if(PyString_Check(u)){ 
      buf = PyString_AsString(u);
      len += PyString_Size(u);
   }else if(strbuf_Check(u)){
      buf= ((strbuf*)u)->buff;
      len += ((strbuf*)u)->leng;
   }
   strbuf* sb = (strbuf*)me->ob_type->tp_alloc(me->ob_type, 0);
   if(!sb) return NULL;
   sb->size = sb->leng = len;
   sb->buff = malloc(sizeof(char)*(len+1));
   if(!sb->buff){ Py_DECREF(sb); return NULL; }
   strcpy(sb->buff, me->buff);
   strcpy(sb->buff + me->leng, buf);
   return (PyObject*)sb;
}
static PyObject* strbuf___getitem__(strbuf* me, int i){
   if (i<0) i += me->leng;
   if(i >= me->leng || i<0){
      PyErr_SetString(PyExc_ValueError, "Index out of Range");
      return 0;
   }
   return PyString_FromStringAndSize(me->buff+i,1);
}
static int strbuf___setitem__(strbuf* me, int i, PyObject* v){
   if (i<0) i += me->leng;
   if(i >= me->leng || i<0){
      PyErr_SetString(PyExc_ValueError, "Index out of Range");
      return 1; //failed
   }
   if(!PyString_Check(v) || PyString_Size(v)!=1){ 
      PyErr_SetString(PyExc_TypeError, "Must be a String of size 1.");
      return 1; //failed
   }
   me->buff[i] = PyString_AsString(v)[0];
   return 0;
}
static PyObject* strbuf___irepeat__(strbuf* me, int t){
   if(t<=0) return NULL;
   int len = me->leng * t;
   if(len > me->size) 
      if(strbuf_enlarge(me, len)<0)
         return NULL; //failed
   char * buff = me->buff;
   for(; t > 0; t--){ 
      strncpy(buff, me->buff, me->leng);
      buff += me->leng;
   }
   me->leng = len;
   me->buff[len] = 0;
   Py_INCREF(me);
   return (PyObject*) me;
}
int strbuf___init__(strbuf* me, int size){
   me->size = size;
   me->leng = 0;
   me->buff = malloc(sizeof(char)*(size+1));
   if(!me->buff) return 1;
   me->buff[me->leng] = 0;
   return 0;
}
static PyObject* strbuf___init___wrap(
   strbuf* me,
   PyObject* _args_
){
    int size;
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "i"
       , &size)) return 0;

    int cxpyret = strbuf___init__(me, size);
    return Py_BuildValue("i", cxpyret);
}

static PyObject* strbuf___repeat__(strbuf* me, int t){
   if (t<=0) return NULL;
   int len = me->leng * t;
   strbuf* sb = (strbuf*)me->ob_type->tp_alloc(me->ob_type, 0);
   if(!sb) return NULL;
   sb->size = sb->leng = len;
   sb->buff = malloc(sizeof(char)*(len+1));
   if(!sb->buff){ Py_DECREF(sb); return NULL; }
   char * buff = sb->buff;
   for(; t > 0; t--){ 
      strcpy(buff, me->buff);
      buff += me->leng;
   }
   return (PyObject*) sb;
}
static PyObject* strbuf___iconcat__(strbuf* me, PyObject* u){
   if(!PyString_Check(u) && !strbuf_Check(u))
     return NULL; //failed
   int len = me->leng;
   const char* buf = NULL;
   if(PyString_Check(u)){ 
      buf = PyString_AsString(u);
      len += PyString_Size(u);
   }else if(strbuf_Check(u)){
      buf = ((strbuf*)u)->buff;
      len += ((strbuf*)u)->leng;
   }
   if(len > me->size) 
      if(strbuf_enlarge(me, len)<0)
         return NULL; //failed
   strcpy(me->buff+me->leng, buf);
   me->leng  = len;
   Py_INCREF(me); //New Reference!
   return (PyObject*) me;
}
static int strbuf___len__(strbuf* me){ return me->leng; }
static struct PyMethodDef strbufMeths[] = {
{"enlarge", (PyCFunction)strbuf_enlarge_wrap, METH_VARARGS,
"enlarge to the given size, or double the capacity,\n"
"whichever is the bigger."},
{NULL}           /* sentinel */ };

static PyMemberDef strbufFields[] = {
{"size", T_INT, offsetof(strbuf, size), 0,
""},
{"leng", T_INT, offsetof(strbuf, leng), 0,
""},
{"buff", T_STRING, offsetof(strbuf, buff), RO,
""},
{NULL}  /* Sentinel */
};

/**Type strbuf is not numeric.**/

/**Type strbuf is not mapping.**/


static PySequenceMethods strbufAsSeq = {
        (inquiry)strbuf___len__,               /*sq_length*/
        (binaryfunc)strbuf___concat__,         /*sq_concat*/
        (ssizeargfunc)strbuf___repeat__,       /*sq_repeat*/
        (ssizeargfunc)strbuf___getitem__,      /*sq_item*/
        (ssizessizeargfunc)0,/*sq_slice*/
        (intobjargproc)strbuf___setitem__,     /*sq_ass_item*/
        (intintobjargproc)0, /*sq_ass_slice*/
        (objobjproc)0,       /*sq_contains*/
        (binaryfunc)strbuf___iconcat__,        /*sq_inplace_concat*/
        (ssizeargfunc)strbuf___irepeat__,      /*sq_inplace_repeat*/
};/*http://docs.python.org/c-api/typeobj.html#PySequenceMethods*/

/* NO PROPS: strbuf */


static PyTypeObject strbufType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "module6.strbuf",       /*tp_name*/
    sizeof(strbuf),          /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)0,       /*tp_dealloc*/
    (printfunc)0,      /*tp_print*/
    (getattrfunc)0,  /*tp_getattr*/
    (setattrfunc)0,  /*tp_setattr*/
    (cmpfunc)0,          /*tp_compare*/
    (reprfunc)0,        /*tp_repr*/
    0,                  /*tp_as_number*/
    &strbufAsSeq,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    (hashfunc)0,        /*tp_hash */
    (ternaryfunc)0,     /*tp_call*/
    (reprfunc)0,         /*tp_str*/
    (getattrofunc)0,/*tp_getattro*/
    (setattrofunc)0,/*tp_setattro*/
    0,                  /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,                  /*tp_flags*/
    "A Sequence type to simulate StringBuffer.",                  /* tp_doc */
    (traverseproc)0,	       /* tp_traverse */
    (inquiry)0,		       /* tp_clear */
    (richcmpfunc)0,	 /* tp_richcompare */
    0,		               /* tp_weaklistoffset */
    (getiterfunc)0,        /* tp_iter */
    (iternextfunc)0,	  /* tp_iternext */
    strbufMeths,                  /* tp_methods */
    strbufFields,                  /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    (descrgetfunc)0,       /* tp_descr_get */
    (descrsetfunc)0,       /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)strbuf___init___wrap,        /* tp_init */
    0,                         /* tp_alloc */
    0,                   /* tp_new */
};/*http://docs.python.org/c-api/typeobj.html*/

static struct PyMethodDef module6Funcs[] = {
{NULL}           /* sentinel */ };


PyMODINIT_FUNC 
initmodule6(void) {
  PyObject *mod;
  
    //Initialize the type table for "strbuf".
    if(strbufType.tp_new == 0) 
         strbufType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&strbufType) < 0) return;

  mod = Py_InitModule3("module6", module6Funcs, 
     "Sequence extension type with expy-cxpy.");
  if (mod==0) return;
  
    Py_INCREF(&strbufType);
    PyModule_AddObject(mod, "strbuf", (PyObject *)&strbufType);
    /* class fields for class strbuf: */
  /*End class strbuf.*/

  /* Global Members */

}

And we have a test session for this module:

ylan@montop:~/expy/Doc/build/lib.linux-i686-2.5$ python
Python 2.5.2 (r252:60911, Jul 31 2008, 17:28:52)
[GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from module6 import *
>>> x = strbuf(100)
>>> x += 'OK!'
>>> x*= 5
>>> x.buff
'OK!OK!OK!OK!OK!'

Create Iterator Types

We will present a relatively complete example, which will provide utilities to test and produce prime numbers, and will factorize an integer via the iterator protocol.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
"""A small module to calculate/test primes."""

from expy import *
#register module
expymodule("prime")

@prologue
def preamble():
   return """
/**This software is under GPL.*/
#include <stdio.h>
#include <math.h>

/*covers 2^31.*/
#define KMAX 5000

static int nap=1;
static int ap[KMAX]={2};
"""

#total number of stored primes
nprimes = gfield(5000)

@function(int)
def isprime(n=int):
   """isprime(n) --> returns:
      -- 1 if n is a prime, 
      -- 0 if n is not, """
   return """{
   int k, m, r;
   if(n<2) return 0;
   k = prime_prime(KMAX-1);
   if(n==2 || n==k) return 1;
   if(n%k==0) return 0;
   for(k=0; ;++k){
      m = ap[k]; 
      r = n/m;
      if(n==r*m) return 0;
      if(r<=m) return 1;
   }
}"""

@function(int) #default return value
def prime(k=int):
    """prime(k) --> the k-th prime number."""
    return """{
  if(k<0 || k>=KMAX){
      PyErr_SetString(PyExc_ValueError, "Out of range!");
      return -1;
  }
  if (k<nap) return ap[k];
  int i, m, n = ap[nap-1];
  while (nap <= k){
    n ++;
    for(m=ap[i=0];
         n%m!=0 && n/m>m;
         m=ap[++i]);
    if (n%m!=0) ap[nap++] = n;
  }
  return n;
}"""

@function(int)
def primes(x=int):
   """primes(x): --> number of primes <= x."""
   return """{
   if (x<2) return 0;
   //don't check even numbers
   if (x%2==0) --x;
   int sum = 1; //as 2 is a prime
   for(; x>2; --x, --x) 
      sum += prime_isprime(x);
   return sum;
}"""

@function(int)
def firstpf(a=int, k=int):
   """return the smallest prime factor k'>=k."""
   return """{
  if(k<0) k=0;
  int p = prime_prime(k);
  int r = a/p;
  while(r>=p){
    if(a == p*r) return k;
    p = prime_prime(++k);
    r = a/p;
  };
  return -1;
}"""

class pfact(public):
   """decompose an integer into prime factors."""
   c = ifield(long, flag='RO', doc='unfactorized part') #readonly
   k = ifield(int, flag='RO', doc='minimal prime order') #readonly
   desc = cfield("enumerate prime factors in low to hi order")

   def __init__(me, a=long):
      return """{
   me->c  = a;
   me->k  = 0;
   return 0; //success
}"""

   #The signature must be like this:
   def __new__(cls, args, kwds):
      """A test of the __new__()"""
      return """{
   pfact* self;
   self = (pfact*)cls->tp_alloc(cls, 0);
   if (self == NULL) return NULL;
   self->k = 0;
   return (PyObject*) self; 
}"""

   def getc(me, closure = raw_type('void*')):
       return """{
   return PyInt_FromLong(me->c);
}"""

   def setc(me, v, closure = raw_type('void*')):
       return """{
   if(!PyInt_Check(v)){ 
      PyErr_SetString(PyExc_TypeError, "Must be an Integer.");
      return 1; //failed
   }
   me->c = PyInt_AsLong(v);
   return me->k = 0; //success
}"""

   rem = property(getc, setc, doc="remainder for factorization")

   def __iter__(me): #special methods should not be expressions
      return """{
   me->k = 0;
   Py_INCREF(me);
   return (PyObject*) me;
}"""

   def __next__(me):
      return """{
   if(me->c <2) return NULL;
   me->k = prime_firstpf(me->c, me->k);
   int n = me->k<0?me->c:prime_prime(me->k);
   me->c /= n;
   return PyInt_FromLong(n);
}"""

   @smethod(int) #static method
   def maxprid(): 
      """maximum prime id."""
      return "KMAX"

   @cmethod(int) #class method
   def maxprime(cls):
      return "prime_prime(KMAX-1)"

#finish up module
expymodule("prime")

In the prime.py, the prologue actually defines some static storage for the primes. This small module is quite useful, it provides the first 5000 primes, and can test any integers within the range of [1, 2^31-1]. And finally, For any integer in that range, it provides the prime factors by the __iter__ and __next__ methods (iterator protocol). Here is the script file to test this module:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import sys
sys.path.insert(0, 'build/lib.linux-i686-2.5')
from prime import *
print pfact.maxprid()
print pfact.maxprime()
print pfact.desc
#this class field is read only:
#pfact.desc = "set to something else"
print "stored primes:", nprimes
for i in range(10): print i, isprime(i), ';',
print
print "there are %i primes from 0 to 9999."%primes(9999)
print "The 10th prime is", prime(10)
pf = pfact(52)
fs = [i for i in pf]
print "The prime factors for 52 are:", fs
print "Property rem:", pf.rem
pf.rem = 123
print "Property rem modified:", pf.rem
print [i for i in pf]

The output from this test file looks like this:

ylan@montop:~/expy/examples$ python testprime.py
5000
48611
enumerate prime factors in low to hi order
stored primes: 5000
0 0 ; 1 0 ; 2 1 ; 3 1 ; 4 0 ; 5 1 ; 6 0 ; 7 1 ; 8 0 ; 9 0 ;
there are 1229 primes from 0 to 9999.
The 10th prime is 31
The prime factors for 52 are: [2, 2, 13]
Property rem: 1
Property rem modified: 123
[3, 41]

And here is the generated C file:

This concludes our introduction to the major features of expy-cxpy.