58. Python mit C erweitern

Manche Aufgaben, die Python in zwei Zeilen lösbar sind, brauchen in C Dutzende und sind schwierig zu machen – dann wieder hätte man gern die rohe Kraft von C. Ideal wäre eine Kombination aus beiden.

Dazu kann man Python-Module in C schreiben. Einfachstes Beispiel: Ein Modul hello, das die Funktion helloWorld exportiert. Das könnte so aussehen:

#include <Python.h>
#include <stdio.h>

static PyObject *hello_printHello(PyObject *self, PyObject *args)
{
  if (!PyArg_ParseTuple(args, "")) {
    return NULL;
  }
  printf("Hello world!\n");
  Py_INCREF(Py_None);
  return Py_None;
}

static PyMethodDef helloMethods[] = {
  {"printHello", hello_printHello, METH_VARARGS,
    "Prints 'Hello World'."},
  {NULL, NULL, 0, NULL},
};

void inithello(void)
{
  Py_InitModule("hello", helloMethods);
}

Python-Module müssen etwas anders gelinkt werden als normale C-Programme, und leider auf jeder Plattform etwas anders. Die Python-Distutils nehmen einem viel von dieser Arbeit ab.

Dazu schreibt man eine Datei setup.py:

from distutils.core import setup, Extension

setup(name = "hello", version = "1.0",
  ext_modules = [
    Extension("hello", ["hello.c"])
    ]
)

und eine weitere Datei setup.cfg

[build_ext]
inplace=1

Zur Namensgebung: Wenn man import hello sagt, sucht Python zunächst nacheinander nach bla.so, blamodule.so und bla.py – module wird also ggf. hinzugefügt (ähnlich wie das lib vom Linker bei der -l-Option), wenn nach Erweiterungsmodulen gesucht wird. Die Extension .so steht für shared object und ist unter Unix für zur Laufzeit ladbare Objektdateien reserviert. Das Pendant unter Windows ist .pyd. Dank der Distutils müssen wir uns darüber aber auch keine Sorgen machen.

Die Distutils-Steuerung erfolgt über das kleine Python-Skript setup.py, das eigentlich nur eine Funktion (ebensetup) aufruft. Diese Funktion bekommt potentiell unzählige Keyword-Argumente, hier etwa den Namen der Distribution (der euch zunächst genauso egal sein kann wie die Version) und eine Liste von Erweiterungsmodulen, die gebaut werden sollen; jedes Modul ist dabei eine Instanz der Klasse Extension, die im einfachsten Fall den Zielnamen und eine Liste der dazu zusammenzubauenden Dateien enthält. Damit lässt sich natürlich viel mehr machen – für die Details verweise ich auf die oben verlinkte vollständige Dokumentation.

Die Aktionen von Setup lassen sich weiter steuern durch die zusätzliche Datei setup.cfg, die keinen Python-Code enthält, sondern in Sektionen unterteilte Key/Value-Paare. Alles, was hier einstellbar ist, wäre auch über Kommandozeilenoptionen machbar, und vieles ist relativ esoterisch. Der oben gezeigte Eintrag dient nur dazu, das Modul, das wir bauen, nicht irgendwo in temporären Verzeichnissen zu verstecken, sondern wirklich in unserem Verzeichnis zu hinterlassen.

Was passiert nun in unserem Modul? Zunächst binden wir die Prototypen für allerlei Python-spezifische Funktionen und “Klassen” ein. Alles, was mit Python zu tun hat, fängt mit Py oder PY an.

Danach definieren wir eine Python-Funktion. Funktionen, die von Python aus aufgerufen werden, habem immer einen Prototypen dieser Art: Sie nehmen zwei PyObjectss und geben ein PyObject zurück. In einem PyObject (in Wirklichkeit eine Struktur) kann irgendein Python-Wert stehen.

Das erste Argument wird dabei nur bei Methoden (also an Objekte gebundene Funktionen) verwendet und entspricht genau dem self, das wir in den Methoden immer verwendet hatten. Das zweite Argument ist ein Python-Tupel, das die Argumente enthält, die an die Python-Funktion übergeben wurden. Diese Argumente werden üblicherweise mit der Funktion PyArg_ParseTuple in C-Variablen übersetzt. PyArg_ParseTuple funktioniert etwa wie sscanf: Im ersten Argument kommt das PyObject, dann ein Formatstring (der hier aber ohne % auskommt, weil keine Schablone nötig ist). Die verschiedenen Formatcodes sind der Dokumentation zu parseTuple zu entnehmen.

Wir wollen hier nur sicher stellen, dass wir kein Argument bekommen und prüfen deshalb auf einen leeren Formatstring. Wenn PyArg_ParseTuple einen Fehler bemerkt (hier also etwa übergebene Argumente, aber z.B. auch falsche Argumente), gibt sie NULL zurück. Das ist das Signal, dass etwas nicht geklappt hat, und wenn eine Funktion NULL an Python zurückgibt, wird eine Exception ausgelöst. Welche, lässt sich beispielsweise mit der Funktion PyErr_SetString angeben – näheres dazu unter Errors and Exceptions. Hier macht das aber bereits PyArg_ParseTuple für uns, wir brauchen nur noch die NULL weiterzureichen, um die Exception auch auszulösen.

Dann tun wir, was wir tun wollen – in dem Fall lediglich ein printf – und geben None zurück, das von C aus als Py None erscheint. Zur INCREF-Magie siehe die nächste Folie.

Als nächstes definieren wir die Schnittstelle zu Python. Wir hatten hello_printHello als static deklariert – wir teilen unseren Namespace hier mit dem Interpreter und möglicherweise anderen Erweiterungen, wollen da also möglichst wenig Namen nach außen sichtbar machen. Damit Python trotzdem weiß, wo es die Funktion findet, definieren wir uns eine Art “Sprungleiste”, eine Tabelle, in der die Funktionen drinstehen, die wir exportieren wollen. In diesem Fall ist das nur eine. Wir initialisieren dabei ein Array von Structs, wobei jeder Struct aus dem Python-Namen der Funktion, einem Pointer auf die Funktion, der Konstante METH_VARARGS (etwas anderes wollt ihr da wirklich nicht stehen haben) sowie einem Docstring für die Funktion besteht. Abgeschlossen wird die Liste durch einen Struct mit lauter Nullen.

Beachtet, dass damit die C-Namen von den Python-Namen völlig entkoppelt sind – wir hätten hello_printHello auch foo nennen können und es Python als printHello verkaufen können. Dennoch ist es eher eine gute Idee, die C-Funktionen so zu nennen, dass sie erahnen lassen, als was sie später ggf. Python zu sehen bekommt. Auch der Namen der Sprungleiste (hier helloMethods) ist beliebig, sollte konventionell aber <modulname>Methods sein.

Dieser Name spielt dann in der einzigen nichtstatischen Funktion des Moduls eine Rollen, inithello. Diese Funktion hat auch als einzige einen festen Namen, nämlich init<modulname>. Sie wird bei einem import des Moduls aufgerufen und kann alles mögliche tun, was das Modul so braucht, bevor irgendwelche Funktionen aufgerufen werden können. Zumeist reicht es aber, die Funktion Py_InitModule mit dem Modulnamen und der Sprungleiste aufzurufen. Sie sorgt dafür, dass Python weiß, welche Fuktionen das Modul nun wirklich exportiert (auf die Sprungleiste kann es ja nicht direkt zugreifen, da sie ja statisch ist).

Wir können dann folgendes tun:

examples> setup.py build
running build_ext
building 'hello' extension
gcc -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC ...
gcc -shared build/temp.linux-i686-2.2/hellomodule.o ...
examples> python
>>> import hello
>>> hello.printHello()
Hello world!
>>> hello.printHello("Quatsch")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: function takes exactly 0 arguments (1 given)
>>> hello.printHello
<built-in function printHello>

Built-in function! Toll.

Übrigens machen die Distutils auch keine schwarze Magie; Python-Erweiterungsmodule können auch mit ganz normalen Bordmitteln gebaut werden, etwa mit einem Makefile dieser Art (hier für Systeme mit den GNU binutils):

PYTHON_COMPILE = -I /usr/local/include/python2.2
CFLAGS += -Wall
CFLAGS += $(PYTHON_COMPILE)

%: %.o
  $(CC) -o $@ $(LDFLAGS) $(CFLAGS) $^

%.so: %.o
  $(CC) -shared $^ -o $@

hellomodule.so: hellomodule.o

Dabei muss dann allerdings der Pfad in PYTHON_COMPILE jeweils per Hand angepasst werden – er könnte z.B. auch /usr/include/python2.2 lauten. Natürlich geht das unter Windows ganz anders, und andere Unixe wollen auch andere Optionen haben. Das ist eine unglückliche Situation, weshalb eben die AutorInnen von Python sich die Distutils ausgedacht haben.

Übungen zu diesem Abschnitt

Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen

(1)

Holt euch den Quellcode des Hello-Moduls von der Vorlesungsseite und baut es mit python hellosetup.py build. Probiert das entstandene Modul. Probiert, ob ihr im etwas interessantere Funktionalität einhauchen könnt.

Dateien zu diesem Abschnitt


Markus Demleitner

Copyright Notice