Python and C

Vincent Bernat

Note

This ar­ti­cle was pub­lished in GNU/Linux Mag­a­zine n° 132 in 2010 in French. It is trans­lated here to Eng­lish with an LLM.

Python is a rich lan­guage with an ex­ten­sive stan­dard li­brary cov­er­ing a wide range of needs. Yet it is not un­com­mon to reach for an ex­ter­nal mod­ule. For ex­am­ple, if your ap­pli­ca­tion needs a Postgre­SQL data­base, you can use the third-party mod­ule psycopg2. Whether or not they are in the stan­dard li­brary, mod­ules fall into two fam­i­lies: na­tive mod­ules, writ­ten en­tirely in Python, and ex­ten­sions, writ­ten in an­other lan­guage, typ­i­cally C. This ar­ti­cle looks at the dif­fer­ent ways to write a Python ex­tension.

An extension or a native module?#

First, why choose to write an ex­ten­sion rather than a na­tive mod­ule? There are ad­van­tages and dis­ad­van­tages to both ap­proaches.

A na­tive mod­ule re­quires no com­pi­la­tion and no ad­di­tional tools. It is por­ta­ble and works iden­ti­cally re­gard­less of the OS: on Mi­cro­soft Win­dows as on a GNU/Linux dis­tri­bu­tion. There is no need to learn an­other lan­guage! By writ­ing your mod­ule in Python, you ben­e­fit from its qual­i­ties: rich­ness, dy­namism, and the many ex­ist­ing mod­ules and ex­ten­sions to build upon.

But it is not al­ways pos­si­ble or de­sir­able to write a na­tive mod­ule. The most com­mon case, and the one we ex­plore later, is us­ing a li­brary writ­ten in an­other lan­guage such as C. You need to bridge this li­brary and Python. An­other rea­son is speed. The most com­mon Python in­ter­preter (CPython) is not yet fast. If speed mat­ters, you may need to re­write cer­tain parts in a more static but faster lan­guage like C. Fi­nally, you may need to ac­cess low-level func­tions that are hard to reach from Python, for ex­am­ple to con­trol an elec­tronic de­vice on an un­com­mon in­ter­face. This last ex­am­ple is less and less rel­e­vant as Python gains ex­ten­sions to fill this need, such as USB de­vice control.

The turtle#

To il­lus­trate this, imag­ine you just re­ceived a ro­bot tur­tle. It is a small ro­bot de­signed to re­pro­duce the be­hav­ior of the LOGO lan­guage tur­tle. The ro­bot can in­ter­pret a very large num­ber of com­mands. You can ask it to move for­ward, turn left, lower the pen, change the pen, and so on. It in­ter­faces with a PC to re­ceive the ap­pro­pri­ate com­mands. The in­ter­face uses a com­mand bus over a se­rial port.

C library#

This tur­tle comes with a C li­brary for con­trol­ling it with a few func­tions. It is a sim­ple li­brary. We use this li­brary rather than try­ing to reim­plement it.

The C interface#

Here is the in­ter­face pro­vided with the library:

#ifndef _TORTUE_H
#define _TORTUE_H

/* Codes d'erreur */
#define TURTLE_ERROR_NO_ERROR      0
#define TURTLE_ERROR_COMMUNICATION 1
#define TURTLE_ERROR_INVALID_VALUE 2
#define TURTLE_ERROR_NOT_PRESENT   3

/* Object opaque représentant une tortue */
struct turtle;

/* Fonctions disponibles */
struct turtle* turtle_init(int port);
int            turtle_send(struct turtle *, const char *);
int            turtle_close(struct turtle *);
int            turtle_error(struct turtle *);
const char*    turtle_model(struct turtle *);
long int       turtle_status(struct turtle *);

#endif

The li­brary can con­trol mul­ti­ple tur­tles si­mul­ta­ne­ously. They are con­nected on a bus and iden­ti­fied by a num­ber. The first tur­tle gets num­ber 1, the next one num­ber 2, and so on.

The turtle_​init() func­tion ini­tial­izes a tur­tle. It re­turns an opaque struc­ture that other func­tions use to iden­tify which tur­tle to work with. If the re­quested tur­tle does not ex­ist, this func­tion re­turns NULL. The in­ter­face does not al­low ob­tain­ing more in­for­ma­tion about the er­ror at this level.

The fol­low­ing func­tions re­turn -1 or NULL on er­ror. You can then get more in­for­ma­tion us­ing the turtle_​error() func­tion. There are only 3 pos­si­ble errors.

The turtle_​send() func­tion sends a com­mand to the tur­tle. It re­turns 0 on suc­cess. You pro­vide it with a string like FORWARD 10 to move it for­ward or LEFT 50 to make it turn. The ro­bot in­ter­prets the com­mand itself.

To shut down the tur­tle and free all as­so­ci­ated re­sources, use the turtle_​close() func­tion. It re­turns 0 on success.

The turtle_​model() func­tion re­turns a string in­di­cat­ing the tur­tle’s model. Fi­nally, the turtle_​status() func­tion re­turns an in­te­ger en­cod­ing the tur­tle’s state. This state de­pends on the model, and the C li­brary pro­vides no in­di­ca­tion of its mean­ing. For more de­tails on this value, you need to read the spec­i­fi­ca­tions of the tur­tle model you are using.

The implementation#

Un­less you have a Tur­tle 3000 deal­er­ship nearby, it is not easy to find the tur­tle de­scribed above. To fa­cil­i­tate ex­per­i­men­ta­tion, we pro­vide a min­i­mal­ist im­ple­men­ta­tion of this li­brary to use through­out the article.

This im­ple­men­ta­tion can con­trol only 3 tur­tles. An er­ror is re­turned if you try to con­trol an­other one. The third tur­tle is also de­fec­tive: you can­not send com­mands to it. Each tur­tle is a dif­fer­ent model.

#include <stdio.h>
#include <stdlib.h>
#include "tortue.h"

FILE *output = NULL;

struct turtle {
  int index;  /* Index de la tortue */
  int error;  /* Dernière erreur de la tortue */
};

struct turtle*
turtle_init(int port)
{
  struct turtle *t;
  if ((port < 1) || (port > 3))
    return NULL;
  t = malloc(sizeof(struct turtle));
  if (!t) return NULL;
  t->index = port;
  t->error = 0;
  fprintf(output, "Open turtle %d\n", t->index);
  fflush(output);
  return t;
}

int
turtle_send(struct turtle *t, const char *command)
{
  if (t->index == 3) {
    t->error = TURTLE_ERROR_COMMUNICATION;
    return -1;
  }
  fprintf(output, "Command for turtle %d: %s\n", t->index, command);
  fflush(output);
  return 0;
}

int
turtle_close(struct turtle *t)
{
  free(t);
  return 0;
}

int
turtle_error(struct turtle *t)
{
  return t->error;
}

const char*
turtle_model(struct turtle *t)
{
  switch (t->index) {
  case 1: return "T1988";
  case 2: return "T2000";
  default: return "T3000";
  }
}

long int
turtle_status(struct turtle *t)
{
  switch (t->index) {
  case 1: return 458751;
  case 2: return 812;
  default: return 0;
  }
}

void __attribute__ ((constructor))
my_init() {
  output = fdopen(3, "a");
  if (!output) output = stderr;
}

We can com­pile our li­brary with the fol­low­ing commands:

$ gcc -O2 -Wall -fPIC -shared -Wl,-soname,libtortue.so.1 \
      -o libtortue.so.1.0.0
$ ln -s libtortue.so.1.0.0 libtortue.so.1
$ ln -s libtortue.so.1 libtortue.so

This li­brary has a small quirk that makes test­ing eas­ier. If file de­scrip­tor 3 ex­ists, it prints di­ag­nos­tic mes­sages there. You can in­ter­cept these mes­sages to ver­ify the proper func­tion­ing of the library.

Be­fore mov­ing on to Python, let us try us­ing our li­brary with a sim­ple test program.

/* sample.c */
#include <assert.h>
#include <stdlib.h>
#include "tortue.h"

int main() {
  struct turtle *t;
  assert((t = turtle_init(1)) != NULL);
  assert(turtle_send(t, "GO 10") == 0);
  assert(turtle_send(t, "LEFT 50") == 0);
  assert(turtle_send(t, "GO 40") == 0);
  assert(turtle_close(t) == 0);
  return 0;
}

Let us com­pile and run.

$ gcc -Wall -O2 -o sample sample.c -L. -ltortue
$ LD_LIBRARY_PATH=. ./sample
Open turtle 1
Command for turtle 1: GO 10
Command for turtle 1: LEFT 50
Command for turtle 1: GO 40

Ev­ery­thing seems to work as expected!

The Python interface#

So you now have a su­perb tur­tle and ev­ery­thing needed to pro­gram it eas­ily in C. But you in­tend this tur­tle for an au­di­ence that wants to pro­gram it in Python rather than C. Two so­lu­tions ex­ist: reim­ple­ment the li­brary as a na­tive Python mod­ule (by read­ing its source code or by lis­ten­ing on the se­rial port) or cre­ate a Python ex­ten­sion. We take the sec­ond approach.

Be­fore div­ing into the ex­ten­sion code, let us de­fine the tar­get in­ter­face. Broadly, we want a Turtle ob­ject with a send() method and model and status at­tributes. Er­rors should be­come ex­ceptions.

To ver­ify that the in­ter­face suits our needs, it is use­ful to write a few lines us­ing the ex­ten­sion be­fore rush­ing into the code. This is where you re­al­ize whether the in­ter­face is com­plete and prac­ti­cal. An­other ap­proach is to write unit tests from the start. This way, we can ver­ify that the code meets our ex­pec­ta­tions and that the dif­fer­ent im­ple­men­ta­tions are equiv­a­lent. Let us dive right into writ­ing these tests!

#!/usr/bin/python

import unittest
import os

r, w = os.pipe()
os.dup2(r, 200)
os.close(r)
os.dup2(w, 3)
os.close(w)
output = os.fdopen(200)

from tortue import Turtle
from tortue import TurtleException

class TestTurtle(unittest.TestCase):

    def test_turtle(self):
        t1 = Turtle(1)
        self.assertEqual(output.readline().strip(), "Open turtle 1")

    def test_send(self):
        t1 = Turtle(1)
        output.readline()
        t1.send("GO 10")
        self.assertEqual(output.readline().strip(), "Command for turtle 1: GO 10")

    def test_model(self):
        t1 = Turtle(1)
        output.readline()
        self.assertEqual(t1.model, "T1988")
        t2 = Turtle(2)
        output.readline()
        self.assertEqual(t2.model, "T2000")

    def test_status(self):
        t1 = Turtle(1)
        self.assertEqual(t1.status["ready"], True)
        self.assertEqual(t1.status["distance"], 458)
        self.assertEqual(t1.status["speed"], 75)

    def test_exception(self):
        t3 = Turtle(3)
        output.readline()
        self.assertRaises(TurtleException, t3.send, "GO 10")

if __name__ == "__main__":
    unittest.main()

We start with a small ma­nip­u­la­tion of file de­scrip­tors to read what comes out of de­scrip­tor 3. This lets us ver­ify the proper func­tion­ing of our ex­ten­sion. We then im­port the ex­ten­sion we want to test and run the 5 tests that val­i­date our in­terface.

The Python implementation#

Our C li­brary is very sim­ple. We could have writ­ten it di­rectly as a Python mod­ule. That is not the point of the ex­er­cise, but to show that the tests work even be­fore writ­ing our first ver­sion of the ex­ten­sion, we will write the Python ver­sion. To do this, we cre­ate a tortue di­rec­tory con­tain­ing a file __init__.py with from native import *. The pur­pose of this file is to di­rect our tortue mod­ule to the right ver­sion of the ex­ten­sion. In this di­rec­tory, we also place the na­tive mod­ule code in the file native.py.

import os
import sys

from status import TurtleStatus

try:
    output = os.fdopen(3, "a")
except OSError:
    output = sys.stdout

class Turtle:
    def __init__(self, nb):
        print >> output, "Open turtle %d" % nb
        output.flush()
        self.nb = nb
        if nb == 1:
            self.model = "T1988"
            self.status = TurtleStatus(self.model, 458751)
        elif nb == 2:
            self.model = "T2000"
            self.status = TurtleStatus(self.model, 812)
        else:
            self.model = "T3000"
            self.status = TurtleStatus(self.model, 0)

    def send(self, cmd):
        if self.nb == 3:
            raise TurtleException("communication error")
        print >> output, "Command for turtle 1: %s" % cmd
        output.flush()

class TurtleException(Exception):
    pass

This mod­ule calls an ad­di­tional mod­ule that de­codes the ro­bot’s state based on its model and the nu­mer­i­cal state value. We want to ob­tain the state as a dic­tio­nary, which is more read­able than a nu­mer­i­cal value. Since the de­cod­ing may evolve with new mod­els, it seems sim­pler to code it in Python rather than in­clud­ing it in the ex­ten­sion. This will also have ed­u­ca­tional value later! The con­tents of the status.py file are as follows:

class TurtleStatus(dict):

    def __init__(self, model, status):
        if model == "T1988":
            dict.__init__(
                self,
                {
                    "ready": (status % 10 == 1),
                    "distance": status / 1000,
                    "speed": (status / 10) % 100,
                },
            )
        else:
            dict.__init__(
                self, {"ready": (status & 2) != 0, "distance": (status & 0xFF0) >> 8}
            )

Once all this is in place, we can run our tests and ev­ery­thing should pass!

$ python turtletest.py
----------------------
Ran 5 tests in 0.000s

OK

But we did not use the C li­brary. If we try to con­trol real tur­tles, not much will hap­pen. It is time to write the ex­ten­sion us­ing the C library.

Writing the Python extension#

There are sev­eral meth­ods for writ­ing a Python ex­ten­sion. We look at four: us­ing ctypes, us­ing SWIG, us­ing Pyrex, and fi­nally writ­ing the ex­ten­sion di­rectly in C with the Python/C API.

Using ctypes#

The ctypes mod­ule lets you in­ter­face with a li­brary and call its func­tions. Its use was al­ready de­tailed in an ar­ti­cle by Vic­tor Stin­ner in a pre­vi­ous spe­cial is­sue. The great ad­van­tage of this ap­proach is ma­nip­u­lat­ing the li­brary in its com­piled form. This yields an ex­ten­sion that re­quires no com­pi­la­tion. The down­side is that there is no safety net: if you make a mis­take or if the bi­nary in­ter­face of the li­brary changes, you get a seg­fault from the Python in­terpreter.

Us­ing the ctypes mod­ule is straight­for­ward. You load the li­brary into mem­ory with CDLL() and then call the func­tions as meth­ods of the ob­tained ob­ject. Be­hind the scenes, the mod­ule fig­ures out the C func­tion’s sig­na­ture from the way you in­voke the Python method.

$ LD_LIBRARY_PATH=. python
Python 2.6.6 (r266:84292, Sep 14 2010, 08:45:25)
[GCC 4.4.5 20100909 (prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import *
>>> libtortue = CDLL("libtortue.so.1")
>>> t1 = libtortue.turtle_init(1)
Open turtle 1
>>> libtortue.turtle_model(t1)
-1600443770

But not ev­ery­thing can hap­pen by magic. In some cases, you need to help the mod­ule. By de­fault, C func­tions are ex­pected to re­turn an int. That is why we get -1600443770 in the ex­am­ple above: a pointer to a char­ac­ter string cast to an in­te­ger. We need to help ctypes by spec­i­fy­ing the re­turn types.

>>> libtortue.turtle_init.restype = c_void_p
>>> libtortue.turtle_model.restype = c_char_p
>>> t1 = libtortue.turtle_init(1)
Open turtle 1
>>> libtortue.turtle_model(t1)
'T1988'

You must not make mis­takes. Oth­er­wise, the con­se­quences are im­me­di­ate. This is why de­vel­op­ing an ex­ten­sion with ctypes is frag­ile and should only be used to build a first prototype.

>>> libtortue.turtle_model(1)
sh: segmentation fault  LD_LIBRARY_PATH=. python

The ctypes mod­ule al­lows many other ma­nip­u­la­tions on C struc­tures. But we do not need them for our ex­tension.

from ctypes import *
from status import TurtleStatus

libtortue = CDLL("libtortue.so.1")
libtortue.turtle_init.restype = c_void_p
libtortue.turtle_model.restype = c_char_p
libtortue.turtle_status.restype = c_long

class Turtle(object):
    def __init__(self, nb):
        t = libtortue.turtle_init(nb)
        if t == 0:
            raise TurtleException("unable to create turtle %d" % nb)
        self.t = c_void_p(t)

    def send(self, cmd):
        result = libtortue.turtle_send(self.t, cmd)
        if result != 0:
            raise TurtleException("got error %d" % libtortue.turtle_error(self.t))

    def __getattribute__(self, attr):
        if attr == "model":
            return libtortue.turtle_model(self.t)
        if attr == "status":
            s = libtortue.turtle_status(self.t)
            if s == -1:
                raise TurtleException("got error %d" % libtortue.turtle_error(self.t))
            return TurtleStatus(self.model, s)
        return object.__getattribute__(self, attr)

    def __del__(self):
        libtortue.turtle_close(self.t)

class TurtleException(Exception):
    pass

Name this new file mctypes.py, then mod­ify the __init__.py file to im­port this file in­stead of native.py. The tests should pass with­out any problems!

Note how sim­ple the in­te­gra­tion is. The send() method calls the C send() func­tion and con­verts any er­ror into an ex­cep­tion. We also use the __getattribute__ method, which in­ter­cepts at­tribute ac­cesses to dy­nam­i­cally pro­vide the cor­rect model and state.

The re­sult­ing mod­ule is frag­ile, though. If an er­ror creeps in, the pro­gram ter­mi­nates with a seg­fault, which is un­usual in Python. This is the na­ture of in­ter­fac­ing Python and C, but the prob­lem is ag­gra­vated by the ab­sence of com­pi­la­tion that would de­tect cer­tain er­rors, in­clud­ing in code paths not ex­er­cised by the tests.

Using SWIG#

SWIG stands for Sim­pli­fied Wrap­per and In­ter­face Gen­er­a­tor. Its goal is to quickly build a Python ex­ten­sion from an in­ter­face file (our tortue.h).

Let us dive right in. To use SWIG, you need to write an in­ter­face file. SWIG will read it to gen­er­ate a C ex­ten­sion that can then be com­piled. Here is the short­est in­ter­face file we can write:

%module swig
%{
#include "../tortue.h"
%}

%include "../tortue.h"

An in­ter­face file con­tains C di­rec­tives as well as SWIG-spe­cific di­rec­tives start­ing with the % sign. The %module di­rec­tive names the ex­ten­sion we want to build. The block de­lim­ited by %{ and %} lets us in­clude ar­bi­trary code in the gen­er­ated C ex­ten­sion. We use it to in­clude our li­brary’s header, as we did for the sam­ple C pro­gram. The %include di­rec­tive in­cludes an ar­bi­trary file in our in­ter­face file. Its for­mat must be un­der­stand­able by SWIG. Our header file tortue.h is sim­ple enough for SWIG to parse di­rectly. If it were too com­plex or con­tained sym­bols we do not want to ex­pose, we would have had to write the func­tion dec­la­ra­tions by hand.

We now com­pile the in­ter­face file into a C file. This C file is then com­piled into a Python ex­ten­sion us­able from the in­terpreter.

$ cd tortue
$ swig -python swig.i
$ gcc -Wall -O2 -c -fPIC swig_wrap.c $(python-config --cflags)
$ gcc -shared swig_wrap.o -L.. -ltortue -o _swig.so
$ cd ..
$ LD_LIBRARY_PATH=. python
>>> from tortue import swig
>>> [f for f in dir(swig) if f.startswith("turtle_")]
['turtle_close', 'turtle_error', 'turtle_init', 'turtle_model', 'turtle_send', 'turtle_status']
>>> a = swig.turtle_init(1)
>>> swig.turtle_send(a, "GO 10")
Command for turtle 1: GO 10
0

SWIG con­verted each dec­la­ra­tion into a Python func­tion that can be used like the cor­re­spond­ing C func­tion. This yields an in­ter­face iden­ti­cal to what you can ob­tain di­rectly with the ctypes mod­ule, but with bet­ter ar­gu­ment check­ing. There is still the pos­si­bil­ity of a seg­fault, but in most cases, er­rors are de­tected correctly:

>>> swig.turtle_model(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in >module<
TypeError: in method 'turtle_model', argument 1 of type 'struct turtle *'

The work is not done, though. The SWIG-gen­er­ated ex­ten­sion does not re­spect our in­ter­face at all. But since the ob­tained in­ter­face is nearly iden­ti­cal to the one from the ctypes mod­ule, we take the mctypes.py file and copy it to mswig.py. Only the be­gin­ning changes:

import swig as libtortue
from status import TurtleStatus

class Turtle(object):
    def __init__(self, nb):
        t = libtortue.turtle_init(nb)
        if t == 0:
            raise TurtleException("unable to create turtle %d" % nb)
        self.t = t

The rest is strictly iden­ti­cal. Mod­ify __init__.py to use this mod­ule and re­run the tests. Ev­ery­thing should pass correctly.

SWIG has fea­tures to pro­duce a di­rectly us­able ex­ten­sion with­out the ad­di­tional wrap­per we had to write here. For ex­am­ple, it can gen­er­ate ex­cep­tions based on func­tion re­turn codes. It can also trans­form struc­tures into ob­jects. An­other fea­ture lets you mod­ify the gen­er­ated func­tions by adding code. This last fea­ture would be needed to re­turn a TurtleStatus in­stance when ac­cess­ing the status at­tribute. We do not de­tail all these fea­tures here be­cause they re­quire some knowl­edge of the Python API, and SWIG is gen­er­ally not used to di­rectly pro­duce a high-level in­ter­face from a low-level C li­brary. Let us see how to ob­tain a Turtle ob­ject rather than a col­lec­tion of functions:

// swig.i
%module swig
%{
#include "../tortue.h"

  typedef struct {
    struct turtle *t;
  } Turtle;

  Turtle *new_Turtle(int port) {
    Turtle *t;
    t = malloc(sizeof(Turtle));
    t->t = turtle_init(port);
    return t;
  }

  void delete_Turtle(Turtle *t) {
    turtle_close(t->t);
    free(t);
  }

  void Turtle_send(Turtle *t, char *cmd) {
    turtle_send(t->t, cmd);
  }

  const char *Turtle_model_get(Turtle *t) {
    return turtle_model(t->t);
  }

  const int Turtle_status_get(Turtle *t) {
    return turtle_status(t->t);
  }
%}

typedef struct {
  %extend {
    Turtle(int);
    ~Turtle();
    void send(char *cmd);
    %immutable;
    const char *model;
    const int status;
  }
} Turtle;

In the C sec­tion, we de­clare a new Turtle struc­ture con­tain­ing only the tur­tle iden­ti­fier. In the SWIG sec­tion, we use the %extend di­rec­tive to trans­form this struc­ture into a class and add meth­ods and at­tributes re­spect­ing our in­ter­face. We thus have a con­struc­tor (equiv­a­lent to __init__), a de­struc­tor (equiv­a­lent to __del__), the send func­tion, and the two model and status at­tributes. The lat­ter are marked as %immutable to in­di­cate that we will not write a func­tion to mod­ify them. SWIG ex­pects to have the cor­re­spond­ing func­tions de­fined in the C section.

This new ex­ten­sion does not pass our tests. Ex­cep­tion han­dling is miss­ing. We would need to de­clare a new ex­cep­tion and use it in each method—this re­quires Python API func­tions that we cover later. The status at­tribute is an in­te­ger whereas we ex­pected a TurtleStatus in­stance. We would need to im­port the mod­ule and in­stan­ti­ate the class—again re­quir­ing Python API functions.

SWIG is very in­ter­est­ing when you have a C li­brary with many func­tions to con­vert, like an OpenGL li­brary. An­other ad­van­tage of SWIG is its com­pat­i­bil­ity with a large num­ber of Python ver­sions, from ver­sion 2.0 up to version 3.

When go­ing fur­ther, you need to call cer­tain Python API func­tions di­rectly. Ad­vanced use of SWIG can quickly be­come dif­fi­cult. For these rea­sons, li­braries us­ing SWIG are typ­i­cally low-level: they pro­vide a Python equiv­a­lent of C func­tions, pos­si­bly with ex­cep­tion han­dling. A more Pythonic in­ter­face is then added by writ­ing an ad­di­tional Python mod­ule on top of the SWIG ex­ten­sion. No knowl­edge of the Python API is truly nec­es­sary to use SWIG.

Using Pyrex#

You can com­bine the free­dom of the ctypes mod­ule with the safety of a com­piler. This so­lu­tion is called Pyrex, a com­piler from Python code to C that can di­rectly use C data.

Pyrex takes Python code as in­put and trans­forms it into C code. Some lim­i­ta­tions ex­ist, but ex­ist­ing Python code can gen­er­ally be trans­formed by Pyrex. Pyrex also knows how to use C data and func­tions, mak­ing it pos­si­ble to mix C and Python code, some­what like the ctypes mod­ule. The ex­ten­sion we are go­ing to write is ac­tu­ally very close to what can be done with the ctypes module:

cdef extern from "../tortue.h":
  struct turtle
  turtle* turtle_init(int port)
  int turtle_send(turtle *, char *)
  int turtle_close(turtle *)
  int turtle_error(turtle *)
  char* turtle_model(turtle *)
  long int turtle_status(turtle *)

from status import TurtleStatus

cdef class Turtle:

    cdef turtle *t

    def __cinit__(self, port):
        cdef turtle* t
        t = turtle_init(port)
        if (t == NULL):
            raise TurtleException("unable to create turtle %d" % port)
        self.t = t

    def send(self, cmd):
        cdef int result
        result = turtle_send(self.t, cmd)
        if result != 0:
            raise TurtleException("got error %d" % turtle_error(self.t))

    def __getattr__(self, attr):
        cdef int s
        if attr == "model":
            return turtle_model(self.t)
        if attr == "status":
            s = turtle_status(self.t)
            if s == -1:
                raise TurtleException("got error %d" % turtle_error(self.t))
            return TurtleStatus(self.model, s)
        return object.__getattribute__(self, attr)

    def __dealloc__(self):
        turtle_close(self.t)

class TurtleException(Exception):
    pass

At the top of the file, we im­port the func­tions we need. The syn­tax is nei­ther Python nor C. You can­not im­port a bunch of func­tions with­out enu­mer­at­ing them one by one. We first de­clare the struct turtle struc­ture, sub­se­quently ref­er­enced by the name turtle. We then de­fine each func­tion fol­low­ing the C pro­to­type as closely as pos­si­ble. Pyrex does not like the const key­word, so we omit it. Once de­fined this way, these C func­tions can be called any­where in the code.

With­out fur­ther spec­i­fi­ca­tion, all vari­ables are as­sumed to con­tain Python ob­jects. Where pos­si­ble, Pyrex han­dles the au­to­matic con­ver­sion of C data into Python ob­jects. For data that can­not be con­verted or for which con­ver­sion is not de­sired (for per­for­mance rea­sons), you must de­clare it with the cdef key­word. Note that the en­tire class uses cdef be­cause we want to store the pointer to our tur­tle in the class, and you can­not store C data in a reg­u­lar Python ob­ject. The __init__ and __del__ meth­ods be­come __cinit__ and __dealloc__ (keep­ing in mind that you should avoid do­ing too many things in the lat­ter). If the C li­brary used an in­te­ger rather than a struc­ture to iden­tify tur­tles, it would have been pos­si­ble to use a reg­u­lar Python class. Fi­nally, note the cu­ri­ous mix of us­ing __getattr__ and __getattribute__ for a class not in­her­it­ing from object. This is a Pyrex pe­cu­liarity.

Let us try to com­pile this code af­ter mod­i­fy­ing __init__.py to use the ex­ten­sion writ­ten with the help of pyrex:

$ pyrexc pyrex.pyx
$ gcc -shared -fPIC -O2 -Wall $(python-config --cflags) pyrex.c \
      -L.. -ltortue -o pyrex.so
$ (cd .. ; LD_LIBRARY_PATH=. python turtletest.py)
.....
----------------------
Ran 5 tests in 0.000s

OK

Pyrex thus com­bines the ad­van­tages of SWIG (com­pi­la­tion, func­tion pa­ram­e­ter check­ing) with the ad­van­tages of the ctypes mod­ule (mix­ing with Python code). But there is no au­to­matic gen­er­a­tion of all func­tions from a li­brary, so con­vert­ing a large li­brary this way is tedious.

Using the Python/C API#

One last op­tion: us­ing the Python/C API to write the en­tire ex­ten­sion di­rectly in C. Be­yond the ed­u­ca­tional as­pect, there are prac­ti­cal rea­sons to con­sider this ap­proach. First, tools like SWIG may re­quire writ­ing more C code than de­sired, code that de­mands a good un­der­stand­ing of the Python API. Sec­ond, if you en­counter a seg­fault or other bug with Pyrex or SWIG, you need to read and un­der­stand the gen­er­ated C code. Fi­nally, you may pre­fer hand-writ­ten code for con­cise­ness, speed, or flex­i­bil­ity. Note that the Python stan­dard li­brary does not ac­cept au­to­mat­i­cally gen­er­ated code.

Memory management#

In C, the pro­gram­mer man­ages mem­ory. You al­lo­cate mem­ory in one place and must re­mem­ber to free it in an­other. The pro­gram­mer is solely in charge and must adopt a method­ol­ogy to avoid for­get­ting to deal­lo­cate un­used mem­ory—ei­ther man­u­ally or us­ing a mech­a­nism such as a garbage col­lec­tor. In Python, mem­ory man­age­ment is au­to­matic. The in­ter­preter main­tains a counter for each ob­ject, in­cre­ments it when a vari­able ref­er­ences the ob­ject, and decre­ments it when no longer ref­er­enced. When the counter reaches zero, the ob­ject is freed.

When writ­ing an ex­ten­sion in C, the ob­jects you cre­ate and ma­nip­u­late can be passed to Python code. You must there­fore use this counter mech­a­nism for each ob­ject you ma­nip­u­late: in­cre­ment the counter to keep an ob­ject and decre­ment it when you no longer need it.

The whole dif­fi­culty lies in know­ing whether it is nec­es­sary to touch an ob­ject’s counter or whether an­other func­tion has al­ready taken care of it. For­tu­nately, there are some con­ven­tions so that the ex­er­cise feels nat­u­ral with a bit of prac­tice. Ev­ery­thing re­volves around the no­tion of own­ing a ref­er­ence to an ob­ject. When you own a ref­er­ence to an ob­ject, you must, when you no longer need it, re­lease it by decre­ment­ing the ob­ject’s counter or trans­fer own­er­ship to an­other func­tion. Con­versely, when you want to use an ob­ject, you must en­sure that you ac­tu­ally own the ref­er­ence to the ob­ject you are ma­nip­ulating.

When you call a func­tion and it re­turns an ob­ject, there are two pos­si­bil­i­ties. Ei­ther the func­tion re­turns a new ref­er­ence to the ob­ject, i.e., del­e­gates own­er­ship of the ref­er­ence to the caller, in which case you should not in­cre­ment the ob­ject’s counter if you want to keep it, but you must decre­ment it when you no longer need it. Or the func­tion lends a ref­er­ence to the ob­ject. This is the op­po­site case. You must in­cre­ment the counter if you wish to keep this ref­er­ence (in or­der to own your own ref­er­ence), and if not, there is no need to decre­ment the counter.

Con­versely, when you pass a ref­er­ence to an ob­ject to a func­tion, it can be­have in two ways. Ei­ther it steals the ref­er­ence to the ob­ject, mean­ing the caller no longer owns the ref­er­ence, or it does not steal it and the caller must dis­pose of the ref­er­ence if they no longer wish to use the object.

Let us start with the sim­ple cases. Un­less oth­er­wise stated in the doc­u­men­ta­tion, a called func­tion does not steal the ref­er­ence to an ob­ject. If the func­tion needs to keep a ref­er­ence to the ob­ject, it will in­cre­ment the ob­ject’s counter it­self. The caller must there­fore dis­pose of the ref­er­ence them­selves in most cases. The two well-known func­tions that steal the ref­er­ence are PyList_​SetItem() and PyTuple_​SetItem(), which place the ob­ject in a list or tuple.

Re­gard­ing the trans­fer or bor­row­ing of the ref­er­ence, a gen­eral rule is that cre­at­ing an ob­ject re­sults in trans­fer­ring own­er­ship of the cor­re­spond­ing ref­er­ence to the caller, while query­ing a prop­erty is merely a bor­row. For ex­am­ple, if you cre­ate a new list with PyList_​New(), own­er­ship of the re­turned ref­er­ence is trans­ferred to the caller. The caller does not need to in­cre­ment the ob­ject’s counter. In this case, we say the caller ob­tains a new ref­er­ence. On the other hand, when you look up an el­e­ment of a list with PyList_​GetItem(), the caller bor­rows the re­turned ref­er­ence. If they wish to keep this ref­er­ence, they must in­cre­ment the ob­ject’s counter them­selves. When in doubt, the doc­u­men­ta­tion in­di­cates each time a func­tion re­turns a Python ob­ject whether own­er­ship is trans­ferred (new ref­er­ence) or bor­rowed in the doc­u­men­ta­tion. C func­tions called from Python must re­turn new ref­erences.

It is not al­ways nec­es­sary to in­cre­ment the counter be­fore us­ing an ob­ject. For ex­am­ple, if this ob­ject is an ar­gu­ment to a func­tion called from the Python in­ter­preter, it is guar­an­teed that the ref­er­ence will be valid for the en­tire life­time of the func­tion. It is there­fore only nec­es­sary to in­cre­ment the counter when you wish to keep this ref­er­ence be­yond the func­tion’s life­time. When the ref­er­ence comes from a func­tion from which you bor­row it, you should be more care­ful. It is pos­si­ble that this ref­er­ence be­comes in­valid be­fore the func­tion ends. In­deed, cer­tain func­tions can trig­ger code that frees the ref­er­ence in ques­tion. It is there­fore prefer­able, in such cases, to in­cre­ment the ob­ject’s counter.

So, how do you ma­nip­u­late this counter? Sev­eral macros ex­ist. Py_​INCREF in­cre­ments the counter, Py_​DECREF decre­ments it, and Py_​XDECREF does the same pro­vided the ob­ject is not NULL (in which case the macro does nothing).

API overview#

Mem­ory man­age­ment is cen­tral to us­ing the Python API prop­erly. Reread the pre­vi­ous sec­tion if it seems un­clear. The rest of the Python API is much sim­pler to un­derstand.

From your ex­ten­sion’s C code, you re­ceive Python ob­jects, ma­nip­u­late them, and re­turn them. Ev­ery­thing that comes from or goes to the Python in­ter­preter is a Python ob­ject, in­clud­ing in­te­gers and strings. When you ma­nip­u­late a Python ob­ject in C, you have a ref­er­ence to it in the form of a PyObject * pointer. Not all the ob­jects you ma­nip­u­late are equiv­a­lent, but they are al­ways rep­re­sented as a PyObject * pointer.

Func­tions are pre­fixed ac­cord­ing to the type of ob­ject they han­dle. For ex­am­ple, PyList_ func­tions ma­nip­u­late lists, while PyInt_ func­tions ma­nip­u­late in­te­gers. For each type, you typ­i­cally have func­tions to cre­ate an ob­ject, check its type (is this ar­gu­ment ac­tu­ally an in­te­ger?), and con­vert it to an­other type. Ev­ery­thing you can do from the Python in­ter­preter has an equiv­a­lent func­tion in the API.

Two func­tions come up reg­u­larly. The first is PyArg_​ParseTuple(), which ver­i­fies that a func­tion’s ar­gu­ments match the ex­pected types and stores them in vari­ables, in a sin­gle call. Did the caller pro­vide an in­te­ger, then a list, then op­tion­ally an­other in­te­ger? If so, store each value in the right vari­able. The sec­ond is Py_​BuildValue(), which makes it easy to con­struct sim­ple ob­jects like an in­te­ger, or a tu­ple con­tain­ing an in­te­ger and a string.

Error handling#

Python has an ex­cep­tion mech­a­nism. When an er­ror oc­curs in an ex­ten­sion, it must raise an ex­cep­tion. The Python API han­dles er­rors in a straight­for­ward way. The gen­eral rule is that when a func­tion that should re­turn a ref­er­ence to an ob­ject re­turns NULL in­stead, an ex­cep­tion is gen­er­ated. In par­tic­u­lar, if a Python API func­tion re­turns NULL and you do not wish to han­dle this er­ror case, also re­turn NULL and the ex­cep­tion will be prop­a­gated with the ap­pro­pri­ate er­ror mes­sage. When a func­tion does not re­turn a Python ob­ject, re­fer to the doc­u­men­ta­tion to see how er­ror cases are han­dled. In all cases, you can call PyErr_​Occurred() to find out whether you are cur­rently in an er­ror state.

When you want to gen­er­ate an ex­cep­tion, you must not only re­turn NULL (or -1 in the case of most func­tions re­turn­ing an in­te­ger) to the in­ter­preter, but also in­di­cate which ex­cep­tion you want to raise. To do this, you can use func­tions start­ing with PyErr_ such as PyErr_​NoMemory(), PyErr_​SetString(), or PyErr_​FormatString().

Writing the extension#

Let us ease into writ­ing our ex­ten­sion. Here is a first ver­sion con­tain­ing its ini­tial­iza­tion and the dec­la­ra­tion of the TurtleException:

#include <Python.h>

static PyObject *TurtleException;

PyMODINIT_FUNC
initpythonc(void)
{
    PyObject *m;

    m = Py_InitModule("pythonc", NULL);
    if (!m)
        return;

    TurtleException = PyErr_NewException("pythonc.TurtleException", NULL, NULL);
    if (!TurtleException)
      return;
    Py_INCREF(TurtleException);
    PyModule_AddObject(m, "TurtleException", TurtleException);
}

The Python.h header con­tains the dec­la­ra­tions needed for the Python API. We then de­clare the TurtleException ob­ject, us­able through­out the ex­ten­sion. The func­tion marked with PyMODINIT_​FUNC ini­tial­izes the mod­ule. A mod­ule is also a Python ob­ject. We ini­tial­ize it with Py_​InitModule(). The first pa­ram­e­ter is the mod­ule name and the sec­ond is the set of mod­ule meth­ods. Cur­rently, we have no meth­ods. Note the er­ror han­dling spe­cific to the API. If Py_​InitModule re­turns NULL, an er­ror oc­curred. Mod­ule ini­tial­iza­tion is some­what spe­cial since the func­tion does not re­turn an ob­ject. We exit the func­tion. The Python in­ter­preter de­tects the er­ror and raises an ex­ception.

Let us re­turn to our TurtleException ob­ject. The PyErr_​NewException() func­tion al­lows us to cre­ate a new ex­cep­tion. Again, we check whether NULL was re­turned, and if so, there is no point go­ing fur­ther. Fol­low­ing the logic de­scribed ear­lier, PyErr_​NewException() should trans­fer a new ref­er­ence on the cre­ated ob­ject to us. The doc­u­men­ta­tion con­firms this. So why do we in­cre­ment the ob­ject’s counter? The next func­tion, PyModule_​AddObject(), whose role is to add an ob­ject to the mod­ule (un­der a name of your choice), steals the ref­er­ence to the ob­ject. But we want to keep this ex­cep­tion be­cause we will use it in the rest of the ex­ten­sion. If the in­ter­preter de­cides to re­move the ex­cep­tion from the mod­ule (with del pythonc.​TurtleException, for ex­am­ple), we could lose the only ref­er­ence to the ex­cep­tion and the ob­ject would be deal­located.

Let us com­pile our new ex­tension:

$ gcc -shared -fPIC -O2 -Wall \
     $(python-config --cflags) pythonc.c -L.. -ltortue \
     -o pythonc.so
$ (cd .. ; LD_LIBRARY_PATH=. python )
>>> from tortue import pythonc
>>> dir(pythonc)
['TurtleException', '__doc__', '__file__', '__name__', '__package__']
>>> raise pythonc.TurtleException
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
pythonc.TurtleException

We must then add our Turtle class to the mod­ule. To do this, we first need to de­clare a struc­ture that will rep­re­sent an in­stance of the class and there­fore the cor­re­spond­ing Python ob­ject. It will no­tably con­tain the counter for track­ing the num­ber of ac­tive ref­er­ences on the ob­ject, as well as any­thing you deem use­ful to as­so­ciate with each in­stance. In our case, our struc­ture is de­clared as follows:

#include "../tortue.h"

typedef struct {
  PyObject_HEAD
  struct turtle *t;
} TurtleObject;

The PyObject_​HEAD macro takes care of in­clud­ing ev­ery­thing nec­es­sary for this struc­ture to be­have as a Python ob­ject, in­clud­ing the ref­er­ence counter. Thus, a vari­able of this struc­ture type can be cast to a PyObject vari­able. We then add the el­e­ments needed for each in­stance. In our case, it is the ref­er­ence to the turtle.

We must then de­fine all pos­si­ble op­er­a­tions on our ob­ject as well as its es­sen­tial char­ac­ter­is­tics. To do this, we de­fine a vari­able of type PyTypeObject that will rep­re­sent the class:

static PyTypeObject TurtleType = {
  PyObject_HEAD_INIT(NULL)
  0,                            /*ob_size*/
  "pythonc.Turtle",             /*tp_name*/
  sizeof(TurtleObject),         /*tp_basicsize*/
  0,                            /*tp_itemsize*/
  (destructor)Turtle_dealloc,   /*tp_dealloc*/
  0,                            /*tp_getattr*/
  0,                            /*tp_setattr*/
  0,                            /*tp_compare*/
  0,                            /*tp_repr*/
  0,                            /*tp_as_number*/
  0,                            /*tp_as_sequence*/
  0,                            /*tp_as_mapping*/
  0,                            /*tp_hash */
  0,                            /*tp_call*/
  0,                            /*tp_str*/
  0,                            /*tp_getattro*/
  0,                            /*tp_setattro*/
  0,                            /*tp_as_buffer*/
  Py_TPFLAGS_DEFAULT,           /*tp_flags*/
  "Turtle objects",             /*tp_doc*/
  0,                            /*tp_traverse*/
  0,                            /*tp_clear*/
  0,                            /*tp_richcompare*/
  0,                            /*tp_weaklistoffset*/
  0,                            /*tp_iter*/
  0,                            /*tp_iternext*/
  0,                            /*tp_methods*/
  0,                            /*tp_members*/
  0,                            /*tp_getset*/
  0,                            /*tp_base*/
  0,                            /*tp_dict*/
  0,                            /*tp_descr_get*/
  0,                            /*tp_descr_set*/
  0,                            /*tp_dictoffset*/
  (initproc)Turtle_init,        /*tp_init*/
  0,                            /*tp_alloc*/
  PyType_GenericNew,            /*tp_new*/
};

For now, our type does not do much. We give it a name, the size of the cor­re­spond­ing ob­ject, and a doc­u­men­ta­tion string. It has no meth­ods or at­tributes yet. But we must ex­plain how to cre­ate a new in­stance us­ing Turtle_​init(). This func­tion serves as __init__(). Let us look at its code:

static int
Turtle_init(TurtleObject *self, PyObject *args, PyObject *kwds)
{
  int port;

  if (!PyArg_ParseTuple(args, "i", &port))
    return -1;

  self->t = turtle_init(port);
  if (!self->t) {
    PyErr_Format(TurtleException, "unable to create turtle %d", port);
    return -1;
  }

  return 0;
}

Note that this func­tion does not re­turn a PyObject *, just like a Python class con­struc­tor. Other than that, the func­tion’s sig­na­ture is stan­dard. All func­tions are called with three ar­gu­ments: a pointer to the in­stance, a pointer to a list of po­si­tional ar­gu­ments, and a pointer to a dic­tio­nary of key­word ar­gu­ments. Gen­er­ally, func­tions re­turn a Python ob­ject. That is not the case here.

We start by ex­am­in­ing the ar­gu­ments re­ceived. We want our con­struc­tor to be called with a sin­gle ar­gu­ment: an in­te­ger rep­re­sent­ing the tur­tle’s port. The role of PyArg_​ParseTuple() is pre­cisely to ver­ify that the po­si­tional ar­gu­ments pro­vided are ex­actly 1 in num­ber and rep­re­sent an in­te­ger. If we were ex­pect­ing a Python ob­ject, we would have used O in­stead of i. We would then also need to check our­selves that the pro­vided ob­ject is of the ex­pected type. For ex­am­ple, if we ex­pected a list, the PyList_​Check() func­tion would con­firm whether we ac­tu­ally have one. If the user pro­vides more than one ar­gu­ment or one that is not an in­te­ger, PyArg_​ParseTuple() will gen­er­ate an ex­cep­tion and re­turn NULL. In this case, we prop­a­gate the ex­cep­tion by re­turn­ing -1. Note that if the user pro­vides key­word ar­gu­ments, those are ig­nored. We could re­place the call to PyArg_​ParseTuple() with a call to PyArg_​ParseTupleAndKeywords() to be stricter.

We then call our C li­brary to ini­tial­ize the tur­tle. If ini­tial­iza­tion fails, we gen­er­ate an ex­cep­tion us­ing PyErr_​Format() and re­turn -1. Oth­er­wise, 0 is re­turned to in­di­cate success.

We must also de­fine Turtle_​dealloc(), called when the ob­ject is deal­lo­cated. In this func­tion, we call turtle_​close():

static void
Turtle_dealloc(TurtleObject *self)
{
  if (self->t)
    turtle_close(self->t);
  self->ob_type->tp_free((PyObject*)self);
}

If we com­pile and run our tests at this point, the first test passes. We are on the right track! Now we add the send() method. We must first write the cor­re­spond­ing function:

static PyObject *
Turtle_send(TurtleObject *self, PyObject *args, PyObject *kwds)
{
  const char *cmd = NULL;
  int result;
  if (!PyArg_ParseTuple(args, "s", &cmd))
    return NULL;

  result = turtle_send(self->t, cmd);
  if (result != 0) {
    PyErr_Format(TurtleException, "got error %d", turtle_error(self->t));
    return NULL;
  }
  Py_INCREF(Py_None);
  return Py_None;
}

Here again, we start by check­ing our ar­gu­ments. We ex­pect ex­actly one string. If that is not the case, we prop­a­gate the ex­cep­tion. We then call the turtle_​send() func­tion from our li­brary and check the re­sult. If it is not sat­is­fac­tory, we gen­er­ate an ex­cep­tion. Oth­er­wise, we must re­turn None. A Python func­tion that does not re­turn a re­sult re­turns None. You must not re­turn NULL, as that would cor­re­spond to an er­ror case! Also note that we in­cre­ment the counter of the Py_​None ob­ject, which is an ob­ject like any other (but of which only one copy ex­ists). Re­mem­ber, by con­ven­tion, meth­ods must re­turn a new ref­er­ence to the in­ter­preter. We there­fore need to in­cre­ment the counter of any Python ob­ject returned.

We then reg­is­ter this new method. All meth­ods of an ob­ject are ref­er­enced in a sin­gle structure:

static PyMethodDef Turtle_methods[] = {
    {"send", (PyCFunction)Turtle_send, METH_VARARGS,
     "Send a command to the turtle"
    },
    {NULL}  /* Sentinel */
};

We pro­vide the method name, the func­tion to call (whose sig­na­ture must match a C func­tion called from the Python in­ter­preter), the ar­gu­ments it ex­pects (here a vari­able num­ber of po­si­tional ar­gu­ments but no key­word ar­gu­ments), and a doc­u­men­ta­tion string. In the TurtleType def­i­ni­tion, we re­place the tp_​methods mem­ber with this struc­ture (in­stead of 0).

Af­ter com­pi­la­tion, only the tests for the model and status at­tributes fail. Let us write the ac­ces­sor func­tions, start­ing with the sim­pler at­tribute:

static PyObject *
Turtle_getmodel(TurtleObject *self, void *closure)
{
  return PyString_FromString(turtle_model(self->t));
}

The model ac­ces­sor is straight­for­ward. It cre­ates a new Python string from the value re­turned by turtle_​model(). The PyString_​FromString() func­tion gives us a new ref­er­ence. We pass this ref­er­ence to the caller, so there is no need to touch the counter. Note that Py_​BuildValue() would also work here.

All at­tribute ac­ces­sors are grouped in a sin­gle structure:

static PyGetSetDef Turtle_getseters[] = {
        {"model",
         (getter)Turtle_getmodel, NULL,
         "model", NULL},
        {NULL}                  /* Sentinel */
};

For each at­tribute, we pro­vide a name, then the func­tion to get the at­tribute’s value, the func­tion to set it (NULL in our case), the doc­u­men­ta­tion string, and ad­di­tional data that would be passed as the last pa­ram­e­ter of the func­tions. In the def­i­ni­tion of the TurtleType type, we re­place the value of the tp_​getset mem­ber with this structure.

Af­ter com­pi­la­tion, only one last test fails! We need to add the status at­tribute. Re­mem­ber, this at­tribute re­turns a TurtleStatus in­stance. We there­fore need to im­port the cor­re­spond­ing mod­ule and in­stan­ti­ate the class—that is, call Python code from our C code. Here is how to im­port the module:

    StatusModule = PyImport_ImportModule("tortue.status");
    if (!StatusModule)
      return;

Add this code at the end of the mod­ule ini­tial­iza­tion. PyImport_​ImportModule() re­turns a new ref­er­ence. We do not add this ref­er­ence to the mod­ule, so we re­tain own­er­ship. No need to in­cre­ment the counter. StatusModule is de­clared as a static PyObject * global vari­able. We can now use this mod­ule in the status accessor:

static PyObject *
Turtle_getstatus(TurtleObject *self, void *closure)
{
  long int status = turtle_status(self->t);
  const char *model = turtle_model(self->t);
  PyObject *cstatus, *istatus;
  if (status == -1) {
    PyErr_Format(TurtleException, "got error %d", turtle_error(self->t));
    return NULL;
  }
  cstatus = PyObject_GetAttrString(StatusModule, "TurtleStatus");
  if (!cstatus)
    return NULL;
  istatus = PyObject_CallFunction(cstatus, "sl", model, status);
  Py_DECREF(cstatus);
  if (!status)
    return NULL;
  return istatus;
}

We first store the model and cur­rent state in two vari­ables. If turtle_​status() re­turns an er­ror, we raise an ex­cep­tion. Then, we re­trieve in cstatus the TurtleStatus class from the mod­ule we im­ported ear­lier. Since a mod­ule is a reg­u­lar ob­ject, we use the generic PyObject_​GetAttrString() func­tion, which re­turns a new ref­er­ence to the ob­ject in ques­tion. Note that af­ter­wards, we will no longer need this ref­er­ence, so as soon as it has been used, we re­lease it by decre­ment­ing the counter.

We now have a ref­er­ence to the class we wish to in­stan­ti­ate. In­stan­ti­a­tion ac­tu­ally con­sists of treat­ing the class as a func­tion and ex­e­cut­ing it. The Python API pro­vides the PyObject_​CallFunction() func­tion for this pur­pose. We give it the ref­er­ence we have to the class, the for­mat of the ar­gu­ments (a string and a long in­te­ger), and the ar­gu­ments them­selves. PyObject_​CallFunction() takes care of ev­ery­thing for us: it con­verts our vari­ables into Python ob­jects and in­stan­ti­ates the class. On fail­ure, we get NULL and prop­a­gate the ex­cep­tion. On suc­cess, we get a new ref­er­ence to the in­stance we are in­ter­ested in and trans­fer it to the caller.

To fin­ish, we need to add the ref­er­ence to this func­tion in the struc­ture where we de­clared the pre­vi­ous at­tribute. It becomes:

static PyGetSetDef Turtle_getseters[] = {
        {"model",
         (getter)Turtle_getmodel, NULL,
         "model", NULL},
        {"status",
         (getter)Turtle_getstatus, NULL,
         "status", NULL},
        {NULL}                  /* Sentinel */
};

Af­ter com­pi­la­tion, all tests pass. Our mod­ule is func­tional! The Python API is rich and we have only scratched the sur­face. This should give you the foun­da­tion to go further.

Automating compilation#

Most so­lu­tions pre­sented in this ar­ti­cle re­quire a com­pi­la­tion step. We ran com­mands man­u­ally to ob­tain the dif­fer­ent ex­ten­sions. The distutils mod­ule pro­vides ev­ery­thing needed to au­to­mate this com­pi­la­tion and in­te­grate it into Python’s mod­ule build and in­stal­la­tion system.

For an ex­ten­sion writ­ten di­rectly in C, you can add the ext_​modules ar­gu­ment to the setup() func­tion call to com­pile your ex­ten­sion. In our case:

from distutils.core import setup, Extension
setup(name="tortue", version="1.0",
      ext_modules=[Extension("tortue.pythonc",
                             libraries = ['tortue']
                             sources = ['tortue/pythonc.c'])])

SWIG and Pyrex of­fer ex­ten­sions to distutils that achieve a sim­i­lar re­sult. Con­sult their doc­u­mentation.

Conclusion#

In­ter­fac­ing a C li­brary with Python code is not that com­pli­cated. De­pend­ing on your pref­er­ences, many so­lu­tions are avail­able. SWIG gen­er­ates a Python equiv­a­lent of your C li­brary; it is then up to you to add some Python to make the re­sult more Pythonic. Pyrex lets you mix Python code with C data and func­tions while main­tain­ing some safety. Knowl­edge of the Python API is not re­quired to build ad­vanced ex­ten­sions. The ctypes mod­ule achieves a sim­i­lar re­sult with­out ac­cess to the li­brary’s source code. It re­quires some pre­cau­tions but gets re­sults quickly that can then be con­sol­i­dated with the other tools. Fi­nally, for the more ad­ven­tur­ous, build­ing an ex­ten­sion with the Python/C API di­rectly re­mains ac­ces­si­ble and gives you com­plete freedom.