C-API: Python list -> C-array -> Python list?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Mittwoch 30. Januar 2008, 16:16

Hoi,

meine bisherigen Gehversuche mit Funktionen in C waren irgendwie einfacher ... :roll:
Weiß von euch jemand wie man eine Liste aus Python in ein C-array bugsiert und eine (neue) Liste wieder zurück nach Python? Insbesondere suche ich auch nach einer Möglichkeit verschachtelte Listen in C-arrays (double*) zu konvertieren.

Mein Versuch:

Code: Alles auswählen

#include <Python.h>
static PyObject *_foo(PyObject *self, PyObject *args) {
  const double *r; 
  
  if (!PyArg_ParseTuple(args, "O", &r))
    return NULL;
  printf("%f\n", r[0]);

  return Py_BuildValue("O", r);
}

static PyMethodDef foo_methods[] = {
	{"_foo",  _foo, METH_VARARGS},
	{NULL, NULL}      /* sentinel */
};

PyMODINIT_FUNC initfoo(void)
{
	Py_InitModule3("foo", foo_methods,
                   foo__doc__);
}
mit folgendem setup-Skript:

Code: Alles auswählen

from distutils.core import setup
from distutils.extension import Extension    
                     
c_ext = Extension('foo',
                  ['foo.c'])
    
setup(
    ....
    ext_modules = [c_ext],
    cmdclass = {}
)
und folgendem Test-Code:

Code: Alles auswählen

import foo

if __name__ == "__main__":
    print foo._foo([3.0, 2.0])
ergibt:
0.000000
[3.0, 2.0]
statt
3.000000
[3.0, 2.0]
.
Dumme Frage: Was muß ich stattdessen verwenden, damit es funktioniert?

Motivation: Ich möchte schneller sein als mit pyrex mit seinen permanenten API-calls und ohne ctypes zu verwenden (obwohl ich sooo kurz davor stehe ;-) ), weil es die API meines Paketes verkomplizieren würde.

Gruß,
Christian
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Mittwoch 30. Januar 2008, 16:35

Wenn du das Objekt mit

Code: Alles auswählen

if (!PyArg_ParseTuple(args, "O", &r))
parst, erhaeltst du ein Zeiger auf ein PyObject. Du musst jetzt selbt schauen, ob das Objekt nicht irgendein Objekt sondern ein PyList-Objekt ist und es dann in ein Array konvertieren.

Hier hatte ich mal eine Liste von Listen von Integers in ein c-Array umgewandelt und wieder zurueck:

Code: Alles auswählen


//Turn a list of lists of integers into a two-dimensional integer array
static int * PyObject_ToSudoku(PyObject *sudo_obj, int len,
                                int *sudo_array)
{
    PyObject *row, *entry;
    int i, j;

    if (!PyList_Check(sudo_obj))
    {
        PyErr_SetString(PyExc_TypeError, "Sudoku must be a list.");
        return 0;
    }
    
    for (i=0; i<len; i++) 
    {
        row = PyList_GetItem(sudo_obj, i);      
        if (!row)  return 0;  //Index error?

        if (!PyList_Check(row))
        {
            PyErr_SetString(PyExc_TypeError, "Sudoku row must be a list.");
            return 0;
        }

        for (j=0; j<len; j++)
        {
            entry = PyList_GetItem(row, j);
            if (!entry)  return 0; //Index error?
                
            sudo_array[i*len + j] = (int) PyInt_AsLong(entry);
            if (PyErr_Occurred())
            {
                PyErr_SetString(PyExc_TypeError,
                                "Sudoku entries must be integers.");
                return 0;
            }
        }
    }

    return sudo_array;
}


//Turn a nxm integer array into a list of lists
static PyObject * IntArrayMN_ToPyList(int *array, int m, int n)
{
    PyObject *row, *plist, *entry;
    int i, j;

    if(!(plist= PyList_New(0)))  return NULL;
        
    for (i=0; i<m; i++)
    {
        if(!(row = PyList_New(0)))  return NULL;
        for (j=0; j<n; j++)
        {
            entry = Py_BuildValue("i", array[i*n + j]);
            if(!entry)  return NULL;
            PyList_Append(row, entry);
        }
        PyList_Append(plist, row);
    }

    return plist;
}
(Keine Ahnung, ob's vlt. nicht auch besser geht...) In meinem Fall war das noetig, da ich eine c-Library angesprochen habe, die mit PyObject natuerlich nicht klar kommt. Wenn man einfach nur seinen Code schneller machen moechte, koennte man ja auch mit dem PyObject arbeiten...
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Mittwoch 30. Januar 2008, 16:42

Hallo Rebecca,

Super! Vielen, vielen Dank! Das ist eine sehr verständliche Erklärung. Jetzt muß ich mich nur noch mit den tausend anderen Kleinigkeiten rumschlagen. ;-)

In meinem Fall ist es notwendig, weil ich ein paar Millionen Rechenschritte machen möchte / muß und nicht ständig API-calls haben möchte. Mein erster Versuch hat zwar auch funktioniert, aber war kein Deut schneller als die pyrex-Lösung, die ich davor hatte - und sie hat mächtig Speicher gefressen. Beides ist auf C-Ebene ja eigentlich unnötig.

Tja, was schwätze ich hier eigentlich?

Nochmals vielen Dank,
Christian
Benutzeravatar
Rebecca
User
Beiträge: 1662
Registriert: Freitag 3. Februar 2006, 12:28
Wohnort: DN, Heimat: HB
Kontaktdaten:

Mittwoch 30. Januar 2008, 19:05

CM hat geschrieben:In meinem Fall ist es notwendig, weil ich ein paar Millionen Rechenschritte machen möchte / muß und nicht ständig API-calls haben möchte. Mein erster Versuch hat zwar auch funktioniert, aber war kein Deut schneller als die pyrex-Lösung, die ich davor hatte - und sie hat mächtig Speicher gefressen. Beides ist auf C-Ebene ja eigentlich unnötig.
Ahja, interessant.

PS: ich mein das wirklich, kann mich nur gerade nicht motivierter ausdruecken :wink:
Offizielles Python-Tutorial (Deutsche Version)

Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Freitag 1. Februar 2008, 11:59

Kurzes Feedback:

Ich bin noch nicht fertig mit der Optimierung, aber es ist wert das Zwischenergebnis zu verkünden. Also, die Lösung, die ich zuvor in pyrex hatte (die reine Pythonlösung erwähne ich besser gar nicht erst ;-) ), hat ca. 1.7 min. und 1.2Gb Speicher gebraucht. Die Lösung in C braucht ca. 2.4 min und ca. 80 Mb Speicher.
Warum ist die pyrex-Lösung schneller? Weil ich das Problem vektorisiert hatte. Deshalb auch der enorme Speicherverbrauch. Meiner Berechnung nach, kann ich das auch in C machen, was mich noch etwas Zeit und ca. 100 Mb zusätzlichen Speicher kosten wird. Die Geschwindigkeit wird dann hoffentlich etwas geringer als bei der pyrex-Lösung sein (aber wohl in derselben Größenordnung).
Das verlang alles noch etwas Hirnschmalz und ausgiebiges Testen. Mal schaun.

Jedenfalls ist der Overhead durch Gebrauch der C-API winzig.

Meine Lösung für das Entpacken der verschachtelten Liste:

Code: Alles auswählen

typedef struct {
    double x;
    double y;
    double z;
} coordinate;

static PyObject *_foo(PyObject *self, PyObject *args) {
  PyObject * py_vectors = NULL; 
  coordinate *vec = NULL;
  unsigned long vsize;
  if (!PyArg_ParseTuple(args, "O", &py_vectors))
    return NULL;
  vsize = PyList_Size(py_vectors);
  for (i=0; i < vsize; i++) {
    dummy_1 = PyList_GetItem(py_vectors, i);
    if (!PyList_Check(dummy_1)) {
        PyErr_SetString(PyExc_TypeError, "argument vectors must be a nested list");
        return NULL;
    }
    dummy_2 = PyList_GetItem(dummy_1, 0);
    vec[i].x = PyFloat_AsDouble(dummy_2);
    dummy_2 = PyList_GetItem(dummy_1, 1);
    vec[i].y = PyFloat_AsDouble(dummy_2);
    dummy_2 = PyList_GetItem(dummy_1, 2);
    vec[i].z = PyFloat_AsDouble(dummy_2);
  }
 
...
}
Ohne Fehlerbenhandlung. Und ich weiß auch nicht, ob jetzt beim copy & paste und leichtem Umschreiben noch alles korrekt ist.

Gruß,
Christian

edit:
PS Inzwischen bin ich bei 0.4 min und ich glaube, da läßt sich noch etwas rausholen ;-) außerdem brauche ich keinen zusätzlichen Speicher (jedenfalls nicht nennenswert).
Antworten