A Dive-In Tutorial on EXPY

  1. A module with some functions
  2. Insert code using tags
  3. Expressions v.s. code blocks
  4. Allow keywords arguments
  5. Customize returned values
  6. Add global attributes
  7. Exception for arguments/returns
  8. Functions invisible to Python
  9. Use ‘rawtype’ for default values
  10. Add pigtails to string arguments

A module with some functions

Suppose we are writing a very simple extension module that provides some global functions, such as a function giving the smaller of two doubles:

1
2
3
4
5
6
7
"""A module with expy-cxpy."""
from expy import * 

@function(double)
def dmax(x=double, y=double(0)):
   """dmax(x,y) --> the bigger of two doubles."""
   return "{ return x>y?x:y; }"

Note that the decorator ‘@function(double)’ declares the return type as ‘double’. More options may be passed to this decorator, such as access, keywords etc.). The Python function dmax takes two double arguments x and y, as specified by the default values double (if no default values given, the argument type is taken as object). When the default value is an instance (the case with argument y), then both the type and the default value for that argument is determined. The call dmax(-1) should be equivalent to dmax(-1,0) (in other words, when used with one argument, dmax gives the positive part of a double value).

The module and the function can have their documentations, which will be automatically taken into the generated code, such that the final python extension will come with proper documentation, exactly the Python way!

Below is a mapping between python types and C types, so the Python function can be mechanically translated into a C function prototype (more can be added as needed):

Python Type C Type C and Python type map
int int int <==> int
long long int long int <==> int
str char * char* <==> str
nullstr char * And: NULL <==> None
unicode Py_UNICOCE * Py_UNICODE* <==> unicode
object PyObject* PyObject* <==> object
float float float <==> float
Int unsigned int unsigned int <==> int
Long unsigned long unsigned long <==> int
byte char char <==> int
Byte unsigned char unsigned char <==> int
char char char <==> str (length=1)
short short int short int <==> int
Short unsigned short unsigned short <==> int
llong long long long long <==> int
Llong unsigned long long unsigned long long <==> int
double double double <==> float
void void void <==> None

For those types that do not have a Python correspondence, they usually takes a Python type whose C type may be cast into that type. For example, a ‘short’ integer would actually take an ‘int’, and then discard the higher bits (Ref: http://docs.python.org/c-api/arg.html). If there are some C types that is not provided above, a special type ‘rawtype’ can be used to specify C types literally. That is often used in special cases where the prototype must contain some native raw types, such as in special functions of extension types. We will discuss that in more details later on.

Suppose the expy package is in place, fire up Python and do this:

>>> import cxpy, expy_module1 as modu
>>> cxpy.generate(modu)
generating source file: module1.c ...success!

Did you notice the “expy_module1” module we never defined here? The special prefix “expy_” is prepended to the module name ‘module1’ here so that the module ‘module1’ get inspected and prepared for code generation. Also note that this happens AFTER we import ‘cxpy’, as the inspector should be set up in place first. If you forgot to do this, an exception would be thrown when you call cxpy.generate. Let’s take a look at the generated source file ‘module1.h’ and ‘module1.c’.

1
2
3
4
5
double module1_dmax(double x, double y);
static PyObject* module1_dmax_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
);
 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
#include <Python.h>
#include "structmember.h"
#include "module1.h"

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

double module1_dmax(double x, double y){ return x>y?x:y; }
static PyObject* module1_dmax_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
){
    double x;
    double y=0;
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "d|d"
       , &x, &y)) return 0;

    /*****Argument Exception Handling*****/

    double cxpyret = PyErr_Occurred()?(double)0:
                 module1_dmax(x, y);

    /*****Return Value Exception Handling*****/

    if(PyErr_Occurred()) return 0;
    return Py_BuildValue("d", cxpyret);
}

static struct PyMethodDef module1Funcs[] = {
{  "dmax", 
  (PyCFunction)module1_dmax_wrap, 
  METH_VARARGS,
  "dmax(x,y) --> the bigger of two doubles."
},
{NULL}           /* sentinel */ };


PyMODINIT_FUNC 
initmodule1(void) { //must be: init<module>
  PyObject *mod;
  
  mod = Py_InitModule3("module1", module1Funcs, 
     "A module with expy-cxpy.");
  if (mod==0) return;
  
  /* Custom Exceptions */
  /* Global Members */

}

The generated code is actually quite readable. A little bit name mangling is used here: the function ‘dmax’ now becomes ‘module1_dmax’, and we also have a wrapper funciton ‘module1_dmax’, to conform to the Python C API. The general rule is that the C function name is prefixed with the module name, connected by an underscore ‘_’, and the wrapper function will be appended with ‘_wrap’. In the future, this behavior should be configurable. The name mangling is mainly due to the lack of name space in C, for other languages, this might not be necessary.

So much for the side talks! Now, let’s enjoy our little fruit of labor with the help of the distutils package:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# File: module1_setup.py
from distutils.core import setup, Extension
import sys #path to 'expy' and 'cxpy' modules
sys.path.insert(1, '../src')
#prepend expy_ before the module name
import cxpy, expy_module1 as modu
cxpy.generate(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 = 'A simple module.',
      ext_modules = [modext])

Note that the source files are in the Doc directory. Now proceed as follows:

ylan@montop:~/expy/Doc$ python module1_setup.py build
generating file: /home/ylan/expy/Doc/module1.c ...success!
running build
running build_ext
building 'module1' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall
   -Wstrict-prototypes -fPIC -I/usr/include/python2.5
   -c module1.c -o build/temp.linux-i686-2.5/module1.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions
   build/temp.linux-i686-2.5/module1.o
   -o build/lib.linux-i686-2.5/module1.so
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 module1 import *
>>> help(dmax)
>>> dmax(2, 3)
3.0

Insert code using tags

If we plan to expose the ‘sqrt()’ function provided in the <math.h> library, we need to include the library. Here is the simple code to get this easily done:

1
2
3
4
5
6
7
8
"""My first module with expy-cxpy.
@head:#include <math.h>"""
from expy import * 
#File name: module1a.py
@function(double)
def sqrt(x=double):
   """sqrt(x) --> the square root of x."""
   return """{ return sqrt(x); }"""

The definition of sqrt(x) is in the <math.h>, which is explicitly included here in the document string for the module. The special doc-tag '@head:’ in the beginning of a line in the document string indicates what follows should be included in the declarations part of the generated code. Similarly, the '@body:’ tag indicates what follows should be included in the implementation part of the generated code. Both tags can also be used in the document strings of functions, methods, and classes, with similar effects (what follows the '@head:’ tag will be literally included into the declaration part immediately before the generated code for that function, method or class; what follows the '@body:’ tag will be literally included into the implementation part immediately before the generated code for that function, method or class. Look also in other examples for the use of these tags.

Expressions v.s. code blocks

If the implementation for a function is only a simple expression (such as a simple call to a library function), the expression can be provided instead of a code block. In the example above, we may do something like this:

@function(double)
def sqrt(x=double):
   return "sqrt(x)"

Note, this time we only provide an expression, instead of a code block that starts with a curly brace “{“. With that, the C function “module1a_sqrt” will not be generated, and the wrapper function “module1a_sqrt_wrap” will directly use the expression as is.

Still more tersely, we can simply use the Python keyword ‘pass’, when the function name and parameters are the same as the function to be wrapped (suppose it is already implemented):

@function(double)
def sqrt(x=double): pass

The ‘pass’ here is equivalent to providing the expression ‘sqrt(x)’.

Allow keywords arguments

One more important feature of function calling in Python is that you can have either positional arguments or keywords arguments. By default, cxpy will generate code for positional arguments calling to a function, yet when you choose to enable keywords arguments for your function, simply provide keywords=True option in the declaration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
"""Introductory module (b) with expy-cxpy."""
#File name: module1b.py
from expy import * 

@function(void, keywords=True)
def sayHi(to=str, me=str):
    return """{
   printf("Hello %s, I am %s, nice to meet you!\\n",
     to, me);
}"""

Take a look at the generated source file ‘module1b.c’.

 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
#include <Python.h>
#include "structmember.h"
#include "module1b.h"

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

void module1b_sayHi(const char* to, const char* me){
   printf("Hello %s, I am %s, nice to meet you!\n",
     to, me);
}
static PyObject* module1b_sayHi_wrap(
   PyObject* _NOUSE,
   PyObject* _args_,
   PyObject* _kwds_
){
    const char* to;
    const char* me;
    static char* _pctrl[] ={"to", "me",  0};

    if(!PyArg_ParseTupleAndKeywords(
       _args_, _kwds_, "ss", 
       _pctrl, &to, &me)) return 0;

    /*****Argument Exception Handling*****/

    if(!PyErr_Occurred()) module1b_sayHi(to, me);

    /*****Return Value Exception Handling*****/

    if(PyErr_Occurred()) return 0;
    return Py_BuildValue("");
}

static struct PyMethodDef module1bFuncs[] = {
{  "sayHi", 
  (PyCFunction)module1b_sayHi_wrap, 
  METH_KEYWORDS,
  ""
},
{NULL}           /* sentinel */ };


PyMODINIT_FUNC 
initmodule1b(void) { //must be: init<module>
  PyObject *mod;
  
  mod = Py_InitModule3("module1b", module1bFuncs, 
     "Introductory module (b) with expy-cxpy.");
  if (mod==0) return;
  
  /* Custom Exceptions */
  /* Global Members */

}

When you have the extension built, you can call it with keywords like this:

sayHi("Jack", "Tom")
sayHi(me="Jack", to="Tom")

Of course, in the first call Tom says hello to Jack, while in the second it is Jack who says hello to Tom.

Customize returned values

If you don’t like to have cxpy automatically wrap up your returned value, you can wrap up your own by declaring the return type to be ‘object’, and in your implementation code, you must return a Python object (or, in case of an expression, evaluate to a Py_Object*):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
"""An introductory module (c) with expy-cxpy."""
#File name: module1.py
from expy import * 

@function(object) # <-- return type: object
def nextInt(x=int):
   """return the next integer following x."""
   return "PyInt_FromLong(x+1)" # <-- your own

@function(str) 
def getTypeStr(y): # <-- type: object
   """get the type of 'y' as a str"""
   return """{
   if (PyInt_Check(y)) return "int";
   if (PyString_Check(y)) return "str";
   if (PyList_Check(y)) return "list";
   if (PyTuple_Check(y)) return "tuple";
   return "object";
}"""

Notice that the returned integer value is wrapped up as an PyInt object, this way the automatic wrap up by expy-cxpy can be avoided. This is very useful when you have some special object type to return. Similarly, if you have some special object type to pass as an argument to your function, you should declare it as an object (the default type). In the same example, we have a function ‘getTypeStr’ that checks on the type of the passed object and return a string describing the type. This is very useful as you can pass anything as an object to the function (for example, you can pass a tuple to simulate an array). Here is the generated source file ‘module1c.c’.

 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
#include <Python.h>
#include "structmember.h"
#include "module1c.h"

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

static PyObject* module1c_nextInt_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
){
    int x;
    //no keywords support

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

    /*****Argument Exception Handling*****/

    PyObject* cxpyret = PyErr_Occurred()?(PyObject*)0:
                 PyInt_FromLong(x+1);

    /*****Return Value Exception Handling*****/

    if(PyErr_Occurred()) return 0;
    //Direct return: had Py_INCREF???
    return cxpyret;
}

char* module1c_getTypeStr(PyObject* y){
   if (PyInt_Check(y)) return "int";
   if (PyString_Check(y)) return "str";
   if (PyList_Check(y)) return "list";
   if (PyTuple_Check(y)) return "tuple";
   return "object";
}
static PyObject* module1c_getTypeStr_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
){
    PyObject* y;
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "O"
       , &y)) return 0;

    /*****Argument Exception Handling*****/

    const char* cxpyret = PyErr_Occurred()?(const char*)0:
                 module1c_getTypeStr(y);

    /*****Return Value Exception Handling*****/

    if(PyErr_Occurred()) return 0;
    return Py_BuildValue("s", cxpyret);
}

static struct PyMethodDef module1cFuncs[] = {
{  "nextInt", 
  (PyCFunction)module1c_nextInt_wrap, 
  METH_VARARGS,
  "return the next integer following x."
},
{  "getTypeStr", 
  (PyCFunction)module1c_getTypeStr_wrap, 
  METH_VARARGS,
  "get the type of 'y' as a str"
},
{NULL}           /* sentinel */ };


PyMODINIT_FUNC 
initmodule1c(void) { //must be: init<module>
  PyObject *mod;
  
  mod = Py_InitModule3("module1c", module1cFuncs, 
     "An introductory module (c) with expy-cxpy.");
  if (mod==0) return;
  
  /* Custom Exceptions */
  /* Global Members */

}

Note that if you choose to wrap your own returned object, expy automaticlaly take care of increasing the reference count on the returned object.

Add global attributes

Defining a global variable in a module is done this way with expy-cxpy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
"""My 2nd module with expy-cxpy.
@head:
#define ERR_WRONG_ARG 2
"""
#File name: module2.py
from expy import * 
maxint = gfield(123456789)
greets = gfield("Hello, World!")
epsilon = gfield(double(1e-19))
ERR_WRONG_ARG = gfield(
   rawtype(int, 'ERR_WRONG_ARG'))

create_globals(globals(),
	MAXINT=12334565,
	GREETS='Hello World!',
	EPSILON=double(1e-19),
	WRONG_ARG_ERR=rawtype(int, 'ERR_WRONG_ARG')
)

And this 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
#include <Python.h>
#include "structmember.h"
#include "module2.h"

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

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


PyMODINIT_FUNC 
initmodule2(void) { //must be: init<module>
  PyObject *mod;
  
  mod = Py_InitModule3("module2", module2Funcs, 
     "My 2nd module with expy-cxpy.");
  if (mod==0) return;
  
  /* Custom Exceptions */
  /* Global Members */
  PyModule_AddObject(mod, "maxint", 
    Py_BuildValue("i", 123456789));
  PyModule_AddObject(mod, "greets", 
    Py_BuildValue("s", "Hello, World!"));
  PyModule_AddObject(mod, "epsilon", 
    Py_BuildValue("d", 1e-19));
  PyModule_AddObject(mod, "ERR_WRONG_ARG", 
    Py_BuildValue("i", ERR_WRONG_ARG));
  PyModule_AddObject(mod, "EPSILON", 
    Py_BuildValue("d", 1e-19));
  PyModule_AddObject(mod, "MAXINT", 
    Py_BuildValue("i", 12334565));
  PyModule_AddObject(mod, "GREETS", 
    Py_BuildValue("s", "Hello World!"));
  PyModule_AddObject(mod, "WRONG_ARG_ERR", 
    Py_BuildValue("i", ERR_WRONG_ARG));

}

This is a very simple module just having some global variables. Note, the convenience function create_globals( globals(), ...) creates a bunch of global variables in just one call, and the number of keyword parameters is unlimited. In the next topic, we will discuss how to declare custom exceptions and how to raise exceptions.

Exception for arguments/returns

You can raise exceptions based on argument or returned values. The syntax is quite intuitive and it is probably not too hard for you to understand this example below:

 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
"""Illustrate the use of exception."""
from expy import *

class MyError(ValueError): pass
class NegError(MyError): pass

@function(int)
@throws('n<0', NegError, 'No negatives!')
@throws('$ret>100', MyError, 'Out of range: %i', '$ret')
def range_check(n = int):
   return "n*n"

@function(str)
@throws('n<0', NegError, 'No nagatives: %s=%i!','desc, n')
@throws('$ret==NULL', code="   PyErr_NoMemory();")
def stringify(n=int, desc = 'n'):
   return """{
   static char s[30]; //64-bit safe
   sprintf(s, "%i", n);
   return s;
}"""

@function(int) 
@throws('den > 500', MyError, "Can't handle it!") 
@throws(True, code="""
   int i = 2;
   while(i < den && (den % i)) i++;
   if (i != den){ //not a prime!
      PyErr_SetString(PyExc_Exception,
         "argument 'den' must be a prime!");
      //don't return yet, wait for reference accounting
   }
""")
@throws(False, code="""
   int i = 2; 
   while(i < $ret && ($ret % i)) i++;
   if (i != $ret){ //not a prime!
      PyErr_SetString(PyExc_Exception,
         "must return a prime!");
   }
""")
def prime_friend(den = int):
    return "den+2"

Firstly, in the @throws decortor a condition (the first argument ‘when’) specifies when an exception should be thrown. The condition can either involve arguments or returned values (but not both, thus it is easy to distinguish them). Arguments are specified simply by their names, while the returned value is represented by the string ‘$ret’. When the condition does not contain ‘$ret’, it is for an argument value exception, and is dealt before knowing the returned value. If the condition has ‘$ret’ there, it is for a returned value exception and is dealt with after the returned value is obtained.

You can also specify ‘what’ to throw (default is ‘Exception’ in Python). Currently, all standard Python Exceptions/Erros can be thrown. EXPY/CXPY also support custom exceptions (Don’t forget to specify the base class, it must be an exception, such as Python ‘Exception’).

There are four degrees of freedom to throw exceptions, with different values to the three arguments (‘when’, ‘msg’, ‘code’) to the @throw decorator:

  1. only provide a condition (in ‘when’) and a fixed message (in ‘msg’, and the ‘code’ argument must be at default in this case);
  2. provide a condition and a format string (in ‘msg’) and the values (in ‘code’) separated by comma;
  3. provide a condition and some native code (in ‘code’) so that you can throw your custom exceptions (the ‘msg’ argument must be at default in this case);
  4. customize condition logic and exception, where the ‘when’ argument must be either True (for argument excaption) or False (for returned value exception), and the native code is supplied in the ‘code’.

Note: the order in which the @throws appear is preserved in the generated code for arguments/returns respectively (exceptions on arguments always goes before those on returns, of course).

  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
#include <Python.h>
#include "structmember.h"
#include "module1f.h"

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

static PyObject* module1f_range_check_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
){
    int n;
    //no keywords support

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

    /*****Argument Exception Handling*****/

    if(!PyErr_Occurred() && (n<0)){ // throws
       PyErr_SetString(module1f_NegError, "No negatives!");
    }

    int cxpyret = PyErr_Occurred()?(int)0:
                 n*n;

    /*****Return Value Exception Handling*****/

    if(!PyErr_Occurred() && (cxpyret>100)){ // throws
       PyErr_Format(module1f_MyError, 
         "Out of range: %i", cxpyret);
    }

    if(PyErr_Occurred()) return 0;
    return Py_BuildValue("i", cxpyret);
}

char* module1f_stringify(int n, const char* desc){
   static char s[30]; //64-bit safe
   sprintf(s, "%i", n);
   return s;
}
static PyObject* module1f_stringify_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
){
    int n;
    const char* desc="n";
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "i|s"
       , &n, &desc)) return 0;

    /*****Argument Exception Handling*****/

    if(!PyErr_Occurred() && (n<0)){ // throws
       PyErr_Format(module1f_NegError, 
         "No nagatives: %s=%i!", desc, n);
    }

    const char* cxpyret = PyErr_Occurred()?(const char*)0:
                 module1f_stringify(n, desc);

    /*****Return Value Exception Handling*****/

    if(!PyErr_Occurred() && cxpyret==NULL){ // exception
          PyErr_NoMemory();
       //cxpy: no need to automatic "return" here
    }

    if(PyErr_Occurred()) return 0;
    return Py_BuildValue("s", cxpyret);
}

static PyObject* module1f_prime_friend_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
){
    int den;
    //no keywords support

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

    /*****Argument Exception Handling*****/

    if(!PyErr_Occurred() && (den > 500)){ // throws
       PyErr_SetString(module1f_MyError, "Can't handle it!");
    }

    if(!PyErr_Occurred()){

   int i = 2;
   while(i < den && (den % i)) i++;
   if (i != den){ //not a prime!
      PyErr_SetString(PyExc_Exception,
         "argument 'den' must be a prime!");
      //don't return yet, wait for reference accounting
   }

    }

    int cxpyret = PyErr_Occurred()?(int)0:
                 den+2;

    /*****Return Value Exception Handling*****/

    if(!PyErr_Occurred()){

   int i = 2; 
   while(i < cxpyret && (cxpyret % i)) i++;
   if (i != cxpyret){ //not a prime!
      PyErr_SetString(PyExc_Exception,
         "must return a prime!");
   }

    }

    if(PyErr_Occurred()) return 0;
    return Py_BuildValue("i", cxpyret);
}

static struct PyMethodDef module1fFuncs[] = {
{  "range_check", 
  (PyCFunction)module1f_range_check_wrap, 
  METH_VARARGS,
  ""
},
{  "stringify", 
  (PyCFunction)module1f_stringify_wrap, 
  METH_VARARGS,
  ""
},
{  "prime_friend", 
  (PyCFunction)module1f_prime_friend_wrap, 
  METH_VARARGS,
  ""
},
{NULL}           /* sentinel */ };


PyMODINIT_FUNC 
initmodule1f(void) { //must be: init<module>
  PyObject *mod;
  
  mod = Py_InitModule3("module1f", module1fFuncs, 
     "Illustrate the use of exception.");
  if (mod==0) return;
  
  /* Custom Exceptions */
  module1f_MyError = PyErr_NewException(
   "module1f.MyError",
   PyExc_ValueError,NULL);
  PyModule_AddObject(mod, "MyError", module1f_MyError);
  module1f_NegError = PyErr_NewException(
   "module1f.NegError",
   module1f_MyError,NULL);
  PyModule_AddObject(mod, "NegError", module1f_NegError);
  /* Global Members */

}

Finally, it is certainly OK to manage exceptions in your own block of function code, and the generated wrapper function would first check if any exception occurred first, and would return NULL immediately upon any exception. The above example is only for the purpose of illustration, below is a more efficient implementation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
"""Illustrate the use of exception."""
from expy import *
@function(int)
@throws('!is_prime(n)', msg="not a prime!")
@throws('!is_prime($ret)', msg="no prime twin!")
def prime_twin(n=int):
   """detect if n and n+2 are both primes.
@body:
int is_prime(int den){
   int i = 2;
   while(i*i < den && (den % i)) i++;
   return (i*i > den && den > 1);
}"""
   return 'n+2'

Functions invisible to Python

If you would like to define a C function that is invisible to Python, declare it as a private function:

from expy import *

@function(int, private)
def funct(x=int):
   return """{
   // A private function, Python won't know it.
   // Implementation goes here...
   return 0;
}"""

@function(int)
def expose(x=int):
   return "util_funct(x)"

In the example above, the first function is declared as private, so that it will not be exposed to Python, and there is only the implementation ‘util_funct’ generated in the C code. Then in the second function, the C function is called, and its functionality is ported to Python in an indirect way.

Use ‘rawtype’ for default values

If you would like to use a literal expression in C (involving some static variables or macros) as your default value to your argument, you can use the special ‘rawtype’ thing in your prototype.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
"""Illustrate the use of rawtype.
@head://declarations
#define _LEN_ 30.0
@body://implementations
#define SQUARE(x) ((x)*(x))
"""
from expy import *

@function(double)
def square_area(side = rawtype(double, '_LEN_')):
    return "SQUARE(side)"

Note that a macro ‘_LEN_’ is defined to represent a double constant in the prologue, which is then used as a default value to the argument ‘side’. This design can be also used to expose some constants to Python (via global fields or class fields, which will be discussed in more details later). 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
#include <Python.h>
#include "structmember.h"
#include "module1d.h"

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

//@body:begin
//implementations
#define SQUARE(x) ((x)*(x))

//@body:end
static PyObject* module1d_square_area_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
){
    double side=_LEN_;
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "|d"
       , &side)) return 0;

    /*****Argument Exception Handling*****/

    double cxpyret = PyErr_Occurred()?(double)0:
                 SQUARE(side);

    /*****Return Value Exception Handling*****/

    if(PyErr_Occurred()) return 0;
    return Py_BuildValue("d", cxpyret);
}

static struct PyMethodDef module1dFuncs[] = {
{  "square_area", 
  (PyCFunction)module1d_square_area_wrap, 
  METH_VARARGS,
  ""
},
{NULL}           /* sentinel */ };


PyMODINIT_FUNC 
initmodule1d(void) { //must be: init<module>
  PyObject *mod;
  
  mod = Py_InitModule3("module1d", module1dFuncs, 
     "Illustrate the use of rawtype.");
  if (mod==0) return;
  
  /* Custom Exceptions */
  /* Global Members */

}

Another good use of ‘rawtype’ is when you need to specify the C type by giving the literal:

def __print__(me, file=rawtype('FILE*'), flags=int):

this is often used for special methods in writing classes/types extensions (see the next topic).

Add pigtails to string arguments

When parsing a string, its length can also be determined. The Python C API provides this convenience. To use this feature, expy-cxpy provides a special use of the ‘rawtype’, that will help take advantage of this feature.

 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
"""Illustrate the use of pigtail."""
from expy import *

@function(int)
def str_length(s = 'string', 
               n = pigtail(6)):
   """
1. the pigtail must follow the pig. 
2. if the pig has default, so should the pigtail.
"""
   return "n"

@function(int)
def no_default(s=str, n=pigtail):
   """
1. the pigtail must follow the pig. 
2. if the pig has no default, neither should the pigtail.
"""
   return "n"

@function(int)
def nullstr_length(s = nullstr(None), 
               n = pigtail(0)):
   """
nullstr is accepts strings and None,
where None corresponds to a NULL pointer.
"""
   return "n"

Note that the pigtail argument is not considered a real argument to the Python function prototype, but it is a real argument to the implementation prototype in C. 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
#include <Python.h>
#include "structmember.h"
#include "module1e.h"

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

static PyObject* module1e_nullstr_length_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
){
    const char* s=NULL;
    int n=0;
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "|z#"
       , &s, &n)) return 0;

    /*****Argument Exception Handling*****/

    int cxpyret = PyErr_Occurred()?(int)0:
                 n;

    /*****Return Value Exception Handling*****/

    if(PyErr_Occurred()) return 0;
    return Py_BuildValue("i", cxpyret);
}

static PyObject* module1e_no_default_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
){
    const char* s;
    int n;
    //no keywords support

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

    /*****Argument Exception Handling*****/

    int cxpyret = PyErr_Occurred()?(int)0:
                 n;

    /*****Return Value Exception Handling*****/

    if(PyErr_Occurred()) return 0;
    return Py_BuildValue("i", cxpyret);
}

static PyObject* module1e_str_length_wrap(
   PyObject* _NOUSE,
   PyObject* _args_
){
    const char* s="string";
    int n=6;
    //no keywords support

    if(!PyArg_ParseTuple(
       _args_, "|s#"
       , &s, &n)) return 0;

    /*****Argument Exception Handling*****/

    int cxpyret = PyErr_Occurred()?(int)0:
                 n;

    /*****Return Value Exception Handling*****/

    if(PyErr_Occurred()) return 0;
    return Py_BuildValue("i", cxpyret);
}

static struct PyMethodDef module1eFuncs[] = {
{  "nullstr_length", 
  (PyCFunction)module1e_nullstr_length_wrap, 
  METH_VARARGS,
  "\n"
"nullstr is accepts strings and None,\n"
"where None corresponds to a NULL pointer.\n"
""
},
{  "no_default", 
  (PyCFunction)module1e_no_default_wrap, 
  METH_VARARGS,
  "\n"
"1. the pigtail must follow the pig. \n"
"2. if the pig has no default, neither should the pigtail.\n"
""
},
{  "str_length", 
  (PyCFunction)module1e_str_length_wrap, 
  METH_VARARGS,
  "\n"
"1. the pigtail must follow the pig. \n"
"2. if the pig has default, so should the pigtail.\n"
""
},
{NULL}           /* sentinel */ };


PyMODINIT_FUNC 
initmodule1e(void) { //must be: init<module>
  PyObject *mod;
  
  mod = Py_InitModule3("module1e", module1eFuncs, 
     "Illustrate the use of pigtail.");
  if (mod==0) return;
  
  /* Custom Exceptions */
  /* Global Members */

}