sebastiano.tronto.net

Source files and build scripts for my personal website
git clone https://git.tronto.net/sebastiano.tronto.net
Download | Log | Files | Refs | README

python-c.md (4687B)


      1 # How to write a Python module in C
      2 
      3 Something I may want to do in the near future is making a tool /
      4 library I am developing in C available in Python. I know it is
      5 possible, but I have never done before, so yesterday I wrote
      6 [my first Python module in C](https://git.tronto.net/python-c),
      7 just to see how hard it is.
      8 
      9 The answer is: it's easy, but the documentation is not great. The
     10 [official guide](https://docs.python.org/3/extending/extending.html)
     11 is quite lengthy, but somehow it does not even explain how to build
     12 the damn thing! Don't get me wrong, I am a fan of the "theory first"
     13 approach, but at some point I would expect a code snippet and a
     14 command that I can just copy-paste on my terminal and see everything
     15 work. Nope!
     16 
     17 So here is my step-by-step tutorial. You can find all the code in
     18 [my git repository](https://git.tronto.net/python-c).
     19 
     20 ## 1. Write your C library
     21 
     22 Well I guess this is actually step zero, but today we are 1-based.
     23 
     24 My beautiful library consists of only one source file `sum.c`:
     25 
     26 ```
     27 int sum(int a, int b) { return a+b; }
     28 ```
     29 
     30 and one header file `sum.h`:
     31 
     32 ```
     33 int sum(int, int);
     34 ```
     35 
     36 ## 2. The adapter code
     37 
     38 As you may expect, you need some kind of glue code between the raw
     39 C library and the Python interpreter. This is largely boilerplate.
     40 I put mine in a file called `sum_module.c`.
     41 
     42 First you include `Python.h` and your library header:
     43 
     44 ```
     45 #define PY_SSIZE_T_CLEAN
     46 #include <Python.h>
     47 
     48 #include "sum.h"
     49 ```
     50 
     51 (I am not sure what the `PY_SSIZE_T_CLEAN` macro does, but the official
     52 tutorial suggets defining it, so I have kept it there.)
     53 
     54 Then for each of your library's function you need a corresponding
     55 wrapper that takes Python objects, converts them to C objects, calls
     56 the functions and converts the results back:
     57 
     58 ```
     59 static PyObject *csum(PyObject *self, PyObject *args) {
     60 	int a, b, result;
     61 
     62 	if (!PyArg_ParseTuple(args, "ii", &a, &b))
     63 		return NULL;
     64 
     65 	result = sum(a, b);
     66 	return PyLong_FromLong(result);
     67 }
     68 ```
     69 
     70 The `PyArg_ParseTuple()` function looks a bit magical. It is a
     71 variadic function that takes the Python objects contained in `args`
     72 and converts them following the given pattern - in this case "ii" for
     73 two integers.
     74 
     75 Then we need to map the wrapper functions to their python name. Here
     76 I chose to call my function `sum_from_c`:
     77 
     78 ```
     79 static PyMethodDef SumMethods[] = {
     80 	{ "sum_from_c", csum, METH_VARARGS, "Sum two integers." },
     81 	{ NULL, NULL, 0, NULL }
     82 };
     83 ```
     84 
     85 Finally, we just need some more boilerplate for creating the module:
     86 
     87 ```
     88 static struct PyModuleDef summodule = {
     89 	PyModuleDef_HEAD_INIT, "sum", NULL, -1, SumMethods
     90 };
     91 
     92 PyMODINIT_FUNC PyInit_sum_module(void) {
     93 	return PyModule_Create(&summodule);
     94 }
     95 ```
     96 
     97 And we are ready to build! Kind of...
     98 
     99 ## 3. Install the Python development packages
    100 
    101 To build the code above you need the `Python.h` header. Depending
    102 on your system, this may be included in the default Python installation
    103 or in a separate package. For example in Void Linux I needed to
    104 install the `pytnon3-devel` package.
    105 
    106 Anyway, once you have installed the correct package, you can check
    107 where this library header file is with
    108 
    109 ```
    110 $ python3-config --includes
    111 ```
    112 
    113 This command will return a string like `-I/usr/include/python3.12`.
    114 Keep it in mind for the next step!
    115 
    116 ## 4. Build the damn thing!
    117 
    118 First, build the C library code:
    119 
    120 ```
    121 cc -c -o sum.o sum.c
    122 ```
    123 
    124 Here `-c` tells the compiler to skip the
    125 [linking](https://en.wikipedia.org/wiki/Linker_(computing)) step -
    126 otherwise it would complain about a missing `main()` function.  The
    127 `cc` command should be, on any UNIX system, a link to either `gcc`
    128 or some other C compiler. You can use `gcc` instead, if you prefer.
    129 
    130 Then we need to build the adapter code to create the actual module:
    131 
    132 ```
    133 $ cc -shared -I/usr/include/python3.12 -o sum_module.so sum.o sum_module.c
    134 ```
    135 
    136 Here the `-shared` option tells the compiler to build a
    137 [shared object](https://en.wikipedia.org/wiki/Shared_library),
    138 the equivalent of a DLL in Windows. This is a compiled library than
    139 can be dynamically loaded into a running program.
    140 
    141 ## 5. Import and run
    142 
    143 And finally, you can open the Python REPL and run your code. From the same
    144 directory where the `sum_module.so` file is:
    145 
    146 ```
    147 >>> import sum_module
    148 >>> sum_module.sum_from_c(23, 19)
    149 42
    150 ```
    151 
    152 Enjoy!
    153 
    154 ## 6. All the rest
    155 
    156 There are still a couple of things I need to check before I can
    157 repeat these steps with a more complex library. Namely, I need
    158 convert more complex data types from Python to C, for example some
    159 function pointers that I am using for
    160 [callback](../2024-06-20-callback-log).  I'll check again the
    161 official documentation when I get to that point, but for now I am
    162 happy that this simple example works!