64. Look als Modul III

In C müssen wir selbst aufräumen:

static void looker_destroy(lookerobject *self)
{
  munmap(self->g_front,
    self->g_back-self->g_front);
  close(self->fd);
  if (self->comparbuf) {
    free(self->comparbuf);
  }
  PyObject_Del(self);
}

Das Ganze muss natürlich noch ein Modul werden:

static PyObject *looker_new(PyObject *self, PyObject *args)
{  char *fname;

  if (!PyArg_ParseTuple(args, "s", &fname)) {
    return NULL;
  }
  return fillLookerObjectStruct(fname);
}

static PyMethodDef lookerMethods[] = {
  {"Looker", looker_new, METH_VARARGS,
    "Creates a new looker instance."},
  {NULL, NULL, 0, NULL},
};

void initLooker(void)
{
  Py_InitModule("Looker", lookerMethods);
}

Der Konstruktor steht hier also etwas allein, nämlich als Funktion im Modul und nicht als Methode der Klasse. Das muss auch mehr oder weniger so sein, weil sein Name nach außen sichtbar ist.

Die Funktion fillLookerObjectStruct, die wir im Konstruktor verwenden, ist eine relativ langweilige Hilfsfunktion, die Teile der alten Initialisierung verwendet und ansonsten die früher globalen Variablen setzt:

static PyObject *fillLookerObjectStruct(char *fname)
{  struct stat sb;
  lookerobject *self;

  self = PyObject_New(lookerobject, &Lookertype);
  if (!self) { /* If this fails, we're too hosed to even bother */
    return NULL; /* setting an error string */
  }

  if ((self->fd = open(fname, O_RDONLY, 0)) < 0 | fstat(self->fd, &sb)) {
    PyErr_SetFromErrno(PyExc_IOError);
    return NULL;
  }
  if ((void *)(self->g_front = mmap(NULL,
          (size_t)sb.st_size,
          PROT_READ,
          MAP_FILE|MAP_SHARED,
          self->fd,
          (off_t)0)) <= (void *)0) {
    PyErr_SetFromErrno(PyExc_IOError);
    return NULL;
  }
  self->g_back = self->g_front + sb.st_size;
  self->dflag = 0;
  self->fflag = 1;
  self->comparbuf = NULL;
  self->string = NULL;
  self->searchcount = 0;
  return (PyObject*)self;
}

Um das Ergebnis als String zurückzugeben (Wunsch 2), müssen wir die alte print_from-Funktion verändern, etwa so:

static PyObject*
collect_from(lookerobject *self, char *front, char *back)
{
  int eol;
  char *strbeg;

  strbeg = front;
  while (front < back && compare(self, front, back,
    self->fflag) == EQUAL) {
    if (compare(self, front, back, self->fflag) == EQUAL) {
      eol = 0;
      while (front < back && !eol) {
        if (*front++ == '\n')
          eol = 1;
      }
    } else
      SKIP_PAST_NEWLINE(front, back);
  }
  return PyString_FromStringAndSize(strbeg, front-strbeg);
}

Damit müssen natürlich auch die darunterliegenden Funktionen etwas verändert werden. Ihr findet den Quelltext im Anhang dieser Seite.

In collect_from packen wir einfach alles, was früher ausgegeben wurde, in einen Python-String. Dazu müssen wir noch nicht mal verstanden haben, was eigentlich alles im look vor sich geht.

In der Tat ist das auch etwas verwickelt, wie ihr seht, wenn ihr die (guten) Kommentare im Original-Quelltext lest. Für Interessierte hier dennoch der grobe Ablauf:

  1. look macht zunächst wirklich eine Binärsuche, wobei es jeweils den Pointer p nimmt, der genau zwischen front und back liegt. Weil das in der Regel kein Zeilenanfang sein wird, läuft er vor einem Vergleich mit dem p zuerst bis nach dem ersten newline (im Makro SKIP_PAST_NEWLINE).
  2. Die Binärsuche bricht ab, wenn das p im Laufe der Binärsuche gleich back wird. Einerseits ist dann nämlich eine Zeile länger als die Hälfte von back - front, und da unsere Zeilen hoffentlich nicht so furchtbar lang ist, bedeutet das, dass wir nicht mehr viel durchsuchen müssen, weil wir zweitens nach der Definition der Binärsuche wissen, dass das gesuchte Element zwischen front und back ist.
  3. Weil wir wissen, dass wir nicht mehr viel durchsuchen müssen (das n aus O(n) ist klein), können wir jetzt linear durchsuchen (das ist die Funktion linear_search). Darin gucken wir hinter jedem Newline, ob wir das gesuchte gefunden haben, ob wir noch davor sind (und weitersuchen wollen) oder ob das, was wir sehen, schon kleiner ist als das, was wir suchen (dann gibt es keine passende Zeile, und wir signalisieren den Fehlschlag).
  4. Wenn wir etwas gefunden haben, geben wir es aus (bzw. zurück), und zwar in collect_from (bzw. im Original print_from). Dabei wollen wir einfach linear Zeile um Zeile auf einen Match prüfen und aufhören, wenn wir entweder back erreicht haben (dahinter kann es per Definitionem keine Matches mehr geben) oder der Vergleich fehlschlägt (auch danach darf nichts mehr sein, das passen würde).

Wenn man lange genug darüber nachdenkt, findet man, dass das äußere if überflüssig ist, weil seine Bedingung nie falsch werden kann (das stimmt aber auch nur, weil compare keine Seiteneffekte hat). Vermutlich war hier irgendwann mal anderer Code, mit dem zusammen die Abfrage Sinn hatte. Merke: Auch veröffentlichter Code ist nicht immer perfekt.

Draußen in der Welt

Wenn ihr den Eindruck habt, dass es relativ haarig ist, Python-Erweiterungen in C zu schreiben, so liegt ihr richtig. Das haben sich auch andere Menschen gedacht. In der Realität wir man darum in der Regel Hilfsmittel verwenden, die die Erzeugung von solchen Erweiterungen leichter machen. Dazu gehört z.B. SWIG, das beim Schreiben von “wrappern” um bestehende C- oder C++-Bibliotheken hilft, oder Pyrex, das die Grenze zwischen Python und C recht erfolgreich verwischt. Googelt danach.

Dateien zu diesem Abschnitt


Markus Demleitner

Copyright Notice