Python list von c aus an Python Skript übergeben

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
thepaffy
User
Beiträge: 8
Registriert: Mittwoch 20. Juli 2011, 13:32

Hallo,

habe mir schon den ganzen Tag die Finger wund getippt und nichts gefunden.

Ich will eine Python list von c aus an ein Python Skript übergeben.

Den Code für die list zu generieren habe ich gefunden und sieht so aus:

Code: Alles auswählen

static PyObject * IntArrayM_ToPyList(double *array,int m){
	PyObject *plist, *entry;
	int i;

	if(!(plist=PyList_New(0))) return NULL;

	for(i=0;i<m;i++){
		entry=Py_BuildValue("d",array[i]);
		if(!entry) return NULL;
		PyList_Append(plist,entry);
	}
	return plist;
}
wenn ich die list aber jetzt per PyObject_CallObject übergeben will wird das Skript nicht ausgeführt.

Wie müsste ich den jetzt daran gehen??

Edit:
auszug der main

Code: Alles auswählen

Py_Initialize();
	PySys_SetPath("/home/user");
	modul = PyImport_ImportModule("berechnen");
	pyarray=IntArrayM_ToPyList(werte,10);

	if(modul){
//		funk = PyObject_GetAttrString(modul, "entscheide"); edit:
                funk = PyObject_GetAttrString(modul, "ausgeben");
		ret = PyObject_CallObject(funk, pyarray);

		Py_DECREF(pyarray);
		Py_DECREF(ret);
		Py_DECREF(funk);
		Py_DECREF(modul);
	}

	Py_Finalize();
und das python skript

Code: Alles auswählen

def ausgeben(a):
     for i in a:
        print a
Gruß

P.S.: Einzelne Werte bekomme ich übergeben
Zuletzt geändert von thepaffy am Mittwoch 20. Juli 2011, 14:52, insgesamt 2-mal geändert.
deets

Was ist denn richtig - "entscheide" als Funktion, oder "ausgeben"?
BlackJack

@thepaffy: Erst einmal fällt auf, dass Du den Rückgabewert von `IntArrayM_ToPyList()` nicht prüfst. Wenn der NULL sein sollte, dann rufst Du damit `PyObject_CallObject()` auf, was man nicht machen sollte.

Ansonsten ist `IntArrayM_ToPyList()` auch ein kleines bisschen eine ”schönwetter Funktion”. Wenn `Py_BuildValue()` fehlschlägt, wird die Funktion verlassen ohne den Referenzzähler von `plist` zu verringern und ob `PyList_Append()` funktioniert hat, wird gar nicht erst überprüft.

Ferner müsste man den Referenzzähler auf alle erzeugten `entry`\s verringern, denn `PyList_Append()` ”stiehlt” keine Referenzen.
thepaffy
User
Beiträge: 8
Registriert: Mittwoch 20. Juli 2011, 13:32

@deets ausgeben ist richtig
sry falsch kopiert.

@BlackJack
du meinst also:

Code: Alles auswählen

static PyObject * IntArrayM_ToPyList(double *array,int m){
        PyObject *plist, *entry;
        int i;

        if(!(plist=PyList_New(0))) return NULL;

        for(i=0;i<m;i++){
                entry=Py_BuildValue("d",array[i]);
                if(!entry){
                   Py_DECREF(entry);
                   return NULL;
                }
                if(!PyList_Append(plist,entry)){
                   Py_DECREF(entry);
                   return NULL;
                }
        }
        return plist;
}
edit:
und in der main:

Code: Alles auswählen

pyarray=IntArrayM_ToPyList(werte,10);
if(!pyarray){
     Py_DECREF(pyarray);
     printf("Fehler beim erstellen der Python list!");
     Py_Finalize();
     return 0;
}
BlackJack

@thepaffy: Nein, beim Test ob `entry` NULL ist macht es keinen Sinn zu versuchen eine Referenz von `entry` zu verringern wenn es NULL ist. An der Stelle müsste aber der Referenzzähler von `plist` verringert werden. Das gleiche gilt für den Code in `main()` wenn `pyarray` NULL ist.

Und beim zweiten Test sowohl muss man den Referenzzähler von `plist` auch verringern. Und ganz unabhängig davon am Ende der Schleife ohne weitere Bedingungen den von `entry`. Sonst hast Du da ein Speicherleck.

Ich arbeite da in der Regel mit ``goto``, sonst wird das sehr schnell zu verworren, beziehungsweise muss man an jedem möglichen Ausstiegspunkt sonst zu viel Quelltext immer wieder schreiben.
thepaffy
User
Beiträge: 8
Registriert: Mittwoch 20. Juli 2011, 13:32

Aber ich fliege doch eh durch return NULL aus der Funktion. Wieso muss ich dann noch den Refferenzzähler verringern??


Unabhängig davon:

Code: Alles auswählen

static PyObject * IntArrayM_ToPyList(double *array,int m){
        PyObject *plist, *entry;
        int i;

        if(!(plist=PyList_New(0))) return NULL;

        for(i=0;i<m;i++){
                entry=Py_BuildValue("d",array[i]);
                if(!entry){
                   Py_DECREF(plist);
                   return NULL;
                }
                if(!PyList_Append(plist,entry)){
                   Py_DECREF(plist);
                   return NULL;
                }
        }
        return plist;
}
sowie

Code: Alles auswählen

pyarray=IntArrayM_ToPyList(werte,10);
if(!pyarray){
     printf("Fehler beim erstellen der Python list!");
     Py_Finalize();
     return 0;
}
BlackJack

@thepaffy: Wenn Du den Referenzzähler nicht verringerst, wird der Speicher für die entsprechenden Datenstrukturen nicht wieder freigegeben. Was Du mit ”rausfliegen” meinst, ist mir nicht ganz klar? In C muss man sich selbst um die Speicherverwaltung kümmern. Wenn eine Funktion verlassen wird, dann wird automatisch nur der Speicher für lokale Variablen freigegeben. Das sind hier aber immer nur die Zeiger auf die Python-Objekt-Strukturen. Die Strukturen selbst, also die Speicherbereiche auf die die Zeiger verweisen, werden dadurch nicht automatisch mit freigegeben. Wäre auch nicht wünschenswert, denn es könnte ja sein, das die Daten an anderer Stelle noch benötigt werden.

Mit den Referenzzählern und der Fehlerbehandlung sollte man es sehr genau nehmen. Sonst handelt man sich schnell Speicherlecks oder harte Programmabstürze ein.

Es fehlt in der Fehlerbehandlung vom `PyList_Append()` nocht ein `Py_DECREF()` auf `entry` und generell eines für `entry` am Ende der Schleife.

Wird denn der Fehlertext ausgegeben? Ich würde ja nicht einfach selbst einen Fehlertext schreiben, sondern am Ende prüfen ob ein Fehler aufgetreten ist und falls ja die Ausnahme samt Traceback ausgeben lassen. Siehe `PyErr_Occured()` und `PyErr_Print()` im Abschnitt `Exception Handling` in der API-Dokumentation.
thepaffy
User
Beiträge: 8
Registriert: Mittwoch 20. Juli 2011, 13:32

Also die Funktion funktioniert, das hab ich überprüft. Daran kann es nicht liegen.
Mit rausfliegen mein ich, dass durch das durch return die ganze Funktion abgebrochen wird und nicht nur die Iteration der for-Schleife. Ich würde also bei einem Fehler eh nur NULL an pyarray über geben.

habe folgendes als überprüfung gemacht:

Code: Alles auswählen

for(i=0;i<10;i++){
     printf("Vorher: %lf\t\tNachher: %lf\n",werte[i],PyFloat_AsDouble(PyList_GetItem(pyarray,i)));
}
es kamen immer die selben werte raus.
Das Array werte ist ausserdem ein double array was ich vorher mit zufalls Werten füttere.

Code: Alles auswählen

static PyObject * IntArrayM_ToPyList(double *array,int m){
        PyObject *plist, *entry;
        int i;

        if(!(plist=PyList_New(0))) return NULL;

        for(i=0;i<m;i++){
                entry=Py_BuildValue("d",array[i]);
                if(!entry){
                   Py_DECREF(plist);
                   return NULL;
                }
                if(!PyList_Append(plist,entry)){
                   Py_DECREF(plist);
                   Py_DECREF(entry);
                   return NULL;
                }
        }
        Py_DECREF(entry);
        return plist;
}
BlackJack

@thepaffy: Klar wird da `NULL` zurück gegeben und in `pyarray` gespeichert, aber wieso sollte das davon befreien den Referenzzähler auf die Liste zu verringern? Die Liste ist zu dem Zeitpunkt angelegt und verbraucht Speicher.

Das letzte ``Py_DECREF(entry);`` in der Funktion muss *in* die Schleife. Sonst wird das ja nur einmal für die letzte Objekt-Struktur gemacht, und nicht für jede.

Der Fehler ist übrigens beim Aufruf der Funktion. Schau mal in die Dokumentation was `PyObject_CallObject()` als zweites Argument erwartet.

Je nach dem was Du vorhast, ist `PySys_SetPath()` keine so gute Idee. Es sind dann wirklich *nur* noch diese Pfade bekannt. Also in diesem Fall das Heimatverzeichnis. Aber zum Beispiel nicht mehr die Standardbibliothek. Du solltest besser `PySys_SetArgv()` verwenden. Dann wird der Suchpfad um den Pfad des Skripts beziehungsweise dem des aktuellen Arbeitsverzeichnis erweitert.
thepaffy
User
Beiträge: 8
Registriert: Mittwoch 20. Juli 2011, 13:32

Code: Alles auswählen

static PyObject * IntArrayM_ToPyList(double *array,int m){
        PyObject *plist, *entry;
        int i;

        if(!(plist=PyList_New(0))) return NULL;

        for(i=0;i<m;i++){
                entry=Py_BuildValue("d",array[i]);
                if(!entry){
                   Py_DECREF(plist);
                   return NULL;
                }
                if(!PyList_Append(plist,entry)){
                   Py_DECREF(plist);
                   Py_DECREF(entry);
                   return NULL;
                }
                Py_DECREF(entry);
        }
        return plist;
}
Ok die Funktion erwatet ein tuple.
Müsst ich dann mit erst umwandeln und dann abschicken, also ungefähr so PyObject_CallObject(funk,Py_BuildValue("O",pyarray))
BlackJack

@thepaffy: Ich würde eher eine andere Funktion für den Aufruf verwenden. So hat Dein Aufruf wieder ein Speicherleck, weil die `PyObject`-Datenstruktur, die von `Py_BuildValue()` zurück gegeben wird, nicht wieder freigegeben wird. Da muss nach dem Aufruf der Referenzzähler verringert werden. Ausserdem wird dort *so* kein Tupel zurück gegeben, weil die Format-Zeichenkette nur einen Typ enthält. In dem Falle wird eben dieses eine Objekt zurück gegeben. Sonst hättest Du in der Array-Umwandlungsfunktion ja auch eine Liste mit Tupeln, die jeweils ein einzelnes Python-`float`-Exemplar enthalten und nicht eine Liste von `float`-Exemplaren.

Für den Aufruf würde sich `PyObject_CallFunctionObjArgs()` anbieten.

Die Umwandlungsfunktion würde ich mit etwas konkreteren Funktionen schreiben. Also zum Beispiel nicht mit dem generischen `Py_BuildValue()` weil ja klar ist, dass es `float`\s werden sollen. Da die Länge der Ergebnisliste bekannt ist, kann man das Füllen der Liste mit weniger Aufwand und Prüfungen erledigen:

Code: Alles auswählen

static PyObject* DoubleArray2PyList(double *values, int length)
{
    PyObject *result = NULL;
    PyObject *item = NULL;
    int i;
    
    if ((result = PyList_New(length))) {
        for (i = 0; i < length; ++i) {
            if (!(item = PyFloat_FromDouble(values[i]))) {
                Py_DECREF(result);
                return NULL;
            }
            PyList_SET_ITEM(result, i, item);
        }
    }
    return result;
}
thepaffy
User
Beiträge: 8
Registriert: Mittwoch 20. Juli 2011, 13:32

Ok,

dann sag ich mal danke für die promte hilfe.
Wenn ich fragen hab meld ich mich wieder.
thepaffy
User
Beiträge: 8
Registriert: Mittwoch 20. Juli 2011, 13:32

So ich ich hab da noch ein Problem:

main:

Code: Alles auswählen

.....
char verzeichnis[30]="/home/user";
char *vp=&verzeichnis[0];
char **vpp=&vp;
....
Py_Initialize();
PySys_SetArgv(0,vpp);
modul = PyImport_ImportModule("array");
.....
if(modul){
        funk = PyObject_GetAttrString(modul,"ausgeben");
	if(!funk){
		PyErr_Print();
	}
	ret = PyObject_CallFunctionObjArgs(funk,pyarray,NULL);
	if(!ret){
		PyErr_Print();
	}
	Py_DECREF(pyarray);
	Py_DECREF(ret);
	Py_DECREF(funk);
	Py_DECREF(modul);
}else{
	PyErr_Print();
}
Py_Finalize();
...
sagt das Programm mir beim PyErr_Print von funk:
AttributeError: 'module' object has no attribute 'ausgeben'
Obwohl das Py-Skript im angegebenen Verzeichnis liegt
und wie folgt aussieht:

Code: Alles auswählen

def ausgeben(a):
    for i in a:
	print i
    return a
Woran kann das liegen?
Bei diesem Bsp funktioniert es ja auch:
http://openbook.galileocomputing.de/pyt ... fb0e8d423a
thepaffy
User
Beiträge: 8
Registriert: Mittwoch 20. Juli 2011, 13:32

villt sollte ich nicht module so bezeichnen wenns die schon mit dem namen gibt
BlackJack

@thepaffy: Du gibst kein Verzeichnis an, sondern den Namen des Skripts/Programms. Und `PySys_SetArgv()` setzt dann dessen Pfad an den Anfang von `sys.path`. Dass heisst der Skriptname ist ``user`` und das Verzeichnis ist ``/home/``. Die Dokumentation sagt, dass man bei `PySys_SetArgv()` auch eine leere Zeichenkette als erstes Array-Element übergeben kann. Dann wird die zu `sys.path` hinzugefügt, was zur Folge hat, dass Module auch im aktuellen Arbeitsverzeichnis des Prozesses gesucht werden.

Du erstellst das `argv`-Argument übrigens recht umständlich. Du kannst auch einfach ein Array mit Zeichenketten mit nur einem Variablennamen erstellen:

Code: Alles auswählen

    static char *vpp[] = {""};
/* ... */
    PySys_SetArgv(0, vpp);
Unabhängig davon ist es wirklich manchmal nicht gut ein Modul aus der Standardbibliothek durch ein eigenes zu "verdecken".
Antworten