Python/C - [[],[],[],...] an Python übergeben

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.
Benutzeravatar
windows97
User
Beiträge: 24
Registriert: Freitag 10. März 2006, 13:34

Guten Tag allerseits!
Ich hab mal wieder ein Prob mit der C/API.
Ich würde gern mit

Code: Alles auswählen

PyObject_CallMethod( pInstance, "setARB", "XXX", YYY)
die Methode

Code: Alles auswählen

def setARB(self, ARBGraph=[[1, 11.20, 15], [2, 6, 15], [3, 6, 50], [5, 12, 1], [6, 0, 0]])
aufrufen.

ARBGraph habe ich mal definiert, damit ihr seht, wie die Übergabe formatiert sein muss.

Code: Alles auswählen

[int, float, int]
Mein Problem liegt zum einen im Aufbau der Liste in C++ (YYY oben).
Baue ich mir da am Besten eine verkettete Liste, die wiederum Elemente enthält. -> Oder gibt es da bessere Lösungen?
Zum Anderen weiß ich nicht, wie ich XXX dann formatiere. Eigentlich müsste es doch "[[ifi]]" sein, gell?

Ich sag schonmal "Danke"!
Benutzeravatar
windows97
User
Beiträge: 24
Registriert: Freitag 10. März 2006, 13:34

Nach einer kurzen Nacht und viel Kaffee dann hier die Lösung für evtl. Interessierte.

Code: Alles auswählen

int powersupply::setARB( ARB_Point Graph[], int ARB_Length )
{
	PyObject *response = NULL;
	PyObject *PyArgs, *PyARB_Point;
	PyArgs = PyList_New(0);
	// Append the Port-States to the PythonList PyArgs
	for ( int i = 0; i < ARB_Length; i++ )
	{
		PyARB_Point = PyList_New(0);
		PyList_Append( PyARB_Point, Py_BuildValue ( "i", Graph[i].Position ));
		PyList_Append( PyARB_Point, Py_BuildValue ( "f", Graph[i].Voltage ));
		PyList_Append( PyARB_Point, Py_BuildValue ( "i", Graph[i].Time ));
		PyList_Append( PyArgs, PyARB_Point );
	}
	response = PyObject_CallMethodObjArgs( pInstance, Py_BuildValue( "s", "setARB" ), PyArgs, NULL);
	if ( response == NULL )
	{
		return ERROR_RETURN;
	}
	else 
	{
		Py_DECREF( response );
		if ( PyArgs != NULL )		Py_DECREF( PyArgs );
		if ( PyARB_Point != NULL )	Py_DECREF( PyARB_Point );
		return 0;
	}
}
Gruß Daniel
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

Statt Py_BuildValue wäre doch PyInt_FromLong bzw. PyFloat_FromDouble einfacher, oder?
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Auch hier gilt, was ich in einem anderen Thread schon mal gesagt hatte: alle Funktionen die Du bemühst geben einen Fehler-Indikator zurück; zum Beispiel kann es passieren dass Py_BuildValue den Wert aus welchen Gründen auch immer eben nicht bauen kann, oder aber PyList_Append einen Fehler wirft weil zum Beispiel der Speicher zu knapp ist.

Es ohne entsprechende Fehlerprüfung zu lassen produziert fahrlässigerweise Core-Dumps, die man durchaus verhindern könnte, indem man einfach:

Code: Alles auswählen

if( !( data = Py_BuildValue(...) ) )
    goto cleanup;
if( !PyList_Append(PyARB_Point,data) )
    goto cleanup;
data = NULL;
cleanup:
Py_XDECREF(data);
Py_XDECREF(PyARB_Point);
usw. in den Code einbaut. Gute Beispiele hierzu liefern die Python-Module, die aktiven Gebrauch von goto machen um an eine Fehlerbehandlungsstelle zu springen, in der im Endeffekt alle Referenzen freigegeben werden um die Routine nach einem Fehler sauber zu verlassen. So kriegt man nämlich dann den entsprechenden Traceback in Python selbst (zum Beispiel einen MemoryError), und sieht nicht

"Speicherzugriffsfehler, Programm beendet"

auf der Konsole.
--- Heiko.
Benutzeravatar
windows97
User
Beiträge: 24
Registriert: Freitag 10. März 2006, 13:34

Oh, eine goto-Anweisung in C?
--> Da steht jeder dritte Informatik-Prof kurz vorm Herzinfarkt. :lol:
Nichts gegen diese Personengruppe, aber ich hab schon etliche erlebt, die mit erhobenem Finger gepredigt haben, bloß keine GOTOs verwenden.

Danke für die Tipps ich werd sie später mal mit reinbringen. Und dann noch später posten.

Gruß Daniel
Benutzeravatar
windows97
User
Beiträge: 24
Registriert: Freitag 10. März 2006, 13:34

So und hier dann auch die Lösung die hoffentlich allen gefällt.
Ist natürlich etwas größer geworden. Ich hoffe ich habe jetzt alles beachtet.

Code: Alles auswählen

int powersupply::setARB( ARB_Point Graph[], int ARB_Length )
{
	PyObject *PyArgs = NULL, *PyARB_Point = NULL;
	PyObject *PyData = NULL, *PyResponse = NULL;
	PyARB_Point = PyList_New(0);
	PyArgs = PyList_New(0);
	int retValue = -1;
	// Append the Port-States to the PythonList PyArgs
	for ( int i = 0; i < ARB_Length; i++ )
	{
		PyARB_Point = PyList_New(0);
		PyData = PyInt_FromLong( Graph[i].Position );
		// Standard Error Handling
		if ( PyData != NULL )									
		{
			if ( PyList_Append( PyARB_Point, PyData ) == -1 )
			{
				goto error_occured;
			}
		}
		else 
		{
			goto error_occured;
		}
		PyData = PyFloat_FromDouble( Graph[i].Voltage );
		if ( PyData != NULL )
		{
			if ( PyList_Append( PyARB_Point, PyData ) == -1 )
			{ 
				goto error_occured;
			}
		}
		else
		{
			goto error_occured;
		}
		PyData = PyInt_FromLong( Graph[i].Time );
		if ( PyData != NULL )
		{
			if ( PyList_Append( PyARB_Point, PyData ) == -1 )
			{
				goto error_occured;
			}
		}
		else
		{
			goto error_occured;
		}
		if ( PyList_Append( PyArgs, PyARB_Point ) == -1 )
		{
			goto error_occured;
		}
	}
	PyResponse = PyObject_CallMethodObjArgs( pInstance, Py_BuildValue( "s", "setARB" ), PyArgs, NULL);
	if ( PyResponse == NULL )
	{
		goto error_occured;
	}
	else 
	{
		retValue = 0;
	// goto jumps always here to handle the error exception
	error_occured:
		Py_XDECREF( PyResponse );
		Py_XDECREF( PyArgs );
		Py_XDECREF( PyARB_Point );
		Py_XDECREF( PyData );
		return retValue;
	}
}
Gruß Daniel
icepacker
User
Beiträge: 49
Registriert: Dienstag 15. November 2005, 18:48

na das ist jetzt aber echt übel, so viele goto's :shock:
mach doch eine funktion error_occured und lass die dann an den betreffenden
stellen aufrufen...
ansonsten siehts gut aus!
(muss mich auch mal an der kombi C/python versuchen :) )

lg eiswolf
ubuntu linux !!
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Das ist immer noch nicht korrekt. Du prüfst einige Aufrufe immer noch nicht, wie zum Beispiel PyList_New(), o.Ä.

Ich schreib heute abend wenn ich zu Hause bin mal eine Fassung die alle Fehler korrekt abfängt...

Und: eine Funktion aufzurufen ist hier nicht angebracht, das es ja nur darum geht die lokalen Variablen (also den Stack) der Funktion aufzuräumen wenn ein Fehler passiert.
--- Heiko.
icepacker
User
Beiträge: 49
Registriert: Dienstag 15. November 2005, 18:48

modelnine hat geschrieben: Und: eine Funktion aufzurufen ist hier nicht angebracht, das es ja nur darum geht die lokalen Variablen (also den Stack) der Funktion aufzuräumen wenn ein Fehler passiert.
sorry aber,
warum ist das jetzt ein argument gegen eine funktion?
ubuntu linux !!
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Das ist ein Argument eben keine Funktion einzusetzen, da die Funktion die Du aufrufst logischerweise nicht auf den Stack der aufrufenden Funktion (ausser durch ueble Hacks) zugreifen kann. Sprich: die Fehlerbehandlung, und im besonderen das aufraeumen des eigenen Muells, will man im Normalfall nicht weiterdelegieren an eine andere Funktion, sondern selbst erledigen.
--- Heiko.
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

Wenn du schon C++ nutzt, warum wirfst du nicht einfach eine Exception, wenn ein Fehler auftritt und fängst diese dann ab? Dann sparst du dir das goto
Benutzeravatar
windows97
User
Beiträge: 24
Registriert: Freitag 10. März 2006, 13:34

Joah, dass das übelst aussieht, weiß ich selber...
Hab erst überlegt, es mit einer INLINE-FKT zu realisieren, aber dann wärs wohl noch unübersichtlicher geworden (hab ich mir gedacht).

Throw-Catch kann ich auf jeden Fall versuchen. - Danke!

Das PyList_New() teste ich nicht, da hast Recht. - Man, nächstens wird alles in C geschrieben, dann gibts den Rotz net! :wink:

Ich bedanke mich aber schonmal für die rege Beteiligung! *thumpsup*
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

In der folgenden Art und Weise sieht das Ding richtig und gut aus:

Code: Alles auswählen

int powersupply::setARB( ARB_Point Graph[], int ARB_Length )
{
    PyObject *PyArgs = NULL, *PyARB_Point = NULL;
    PyObject *PyData = NULL, *PyResponse = NULL;
    int retValue = -1;

    /* Argumentliste bauen. */
    if( !( PyArgs = PyList_New(0) ) ) goto end_error;

    // Append the Port-States to the PythonList PyArgs
    for ( int i = 0; i < ARB_Length; i++ )
    {
        if( !( PyARB_Point = PyTuple_New(0) ) ) goto end_error;

        /* Position in Tupel einbauen. */
        if( !( PyData = PyInt_FromLong( Graph[i].Position ) ) ) goto end_error;
        PyTuple_SET_ITEM(PyARB_Point,0,PyData);
        PyData = NULL;

        /* Spannung in Tupel einbauen. */
        if( !( PyData = PyFloat_FromDouble( Graph[i].Voltage ) ) ) goto end_error;
        PyTuple_SET_ITEM(PyARB_Point,1,PyData);
        PyData = NULL;

        /* Zeit in Tupel einbauen. */
        if( !( PyData = PyInt_FromLong( Graph[i].Time ) ) ) goto end_error;
        PyTuple_SET_ITEM(PyARB_Point,2,PyData);
        PyData = NULL;

        if ( PyList_Append( PyArgs, PyARB_Point ) ) goto end_error;
        Py_DECREF(PyARB_Point);
        PyARB_Point = NULL;
    }

    /* Funktion aufrufen. */
    if( !( PyData = Py_BuildValue( "s", "setARB" ) ) ) goto end_error;
    PyResponse = PyObject_CallMethodObjArgs( pInstance, PyData, PyArgs, NULL);
    if( PyResponse ) retValue = 0;

end_error:
    Py_XDECREF(PyResponse);
    Py_XDECREF(PyData);
    Py_XDECREF(PyARB_Point);
    Py_XDECREF(PyArgs);

    /* Fehler-Status zurücksetzen... */
    PyErr_Clear();

    return retValue;
}
Kurz zur Erklärung: alle Python C-API Aufrufe werden jetzt auf Rückgabewert geprüft, und es werden keine Referenzen hängen gelassen. Ich hab die innere Liste durch Tupel ersetzt, weil es dann nämlich ein wenig weniger Fehlerprüfung geben muß; wenn ein Tupel mit PyTuple_New erfolgreich erstellt wurde kann beim Setzen des Tupel-Elements nichts mehr schiefgehen, und diese Methode stiehlt auch gleich die Referenz auf das Python-Objekt was ich setze.

Während ich bei PyList_Append die Referenz explizit freigeben muß, weil PyList_Append eine neue Referenz für die Liste erzeugt (damit das erstellte Tupel irgendwann mal wieder freigegeben wird).

HTH!
--- Heiko.
Benutzeravatar
windows97
User
Beiträge: 24
Registriert: Freitag 10. März 2006, 13:34

Hi Heiko!

Also ich habs jetzt nurmal kurz überflogen! - Hab morgen keine Zeit und werds mir deswegen erst Freitag antun (denke ich).

Aber an dieser Stelle schonmal "Danke!".

Gruß Daniel
BlackJack

windows97 hat geschrieben:Oh, eine goto-Anweisung in C?
--> Da steht jeder dritte Informatik-Prof kurz vorm Herzinfarkt. :lol:
Die anderen 2 sehen das ``goto`` hier kontrolliert als Ersatz für Ausnahmen benutzt wird. Denn das auslösen einer Ausnahme ist im Grunde ja auch eine Sprunganweisung aus dem "strukturierten Programmcode" zu einem "except-Label".
Benutzeravatar
windows97
User
Beiträge: 24
Registriert: Freitag 10. März 2006, 13:34

Hi BlackJack!

Das ist mir schon klar gewesen. Musste nur, wie geschrieben, an den Zeigefinger des Profs denken.

Das mit dem strukturierten Programmcode kommt so langsam glaube ich auch. :roll:
Benutzeravatar
windows97
User
Beiträge: 24
Registriert: Freitag 10. März 2006, 13:34

Hallo Heiko!

Bin jetzt dazu gekommen, deinen Vorschlag mal zu Bearbeiten.
Also Problem Nr. 1 ist, dass ich eine Liste als inneres Array brauche und kein Tuple. - Das liesse sich aber wohl noch lösen.
Das Andere Problem liegt jetzt in meinem Verständnis und zwar:
Warum ist bei nem Tuple das Setzen eines items nicht so fehlerkritisch, als bei einer Liste? Das Stehlen der Referenz habe ich doch analog auch bei der Liste.
Desweiteren habe ich jetzt mal mit dem Workaround
#ifdef _DEBUG
#undef _DEBUG
#include "python.h"
#define _DEBUG
#else
#include "python.h"
#endif
das Debuggen mit MsVC ans Laufen bekommen. Hier zeigt sich, das der Referenzzähler des PyData vor dem XDECREF auf 1 steht und danach 14731456 aufweist. Wodurch ist das zu erklären?
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Warum ist bei nem Tuple das Setzen eines items nicht so fehlerkritisch, als bei einer Liste? Das Stehlen der Referenz habe ich doch analog auch bei der Liste.
Eben nicht. Wenn Du PyList_Append() machst wird von der Liste eine neue Referenz auf das Objekt gespeichert, bei PyTuple_SET_ITEM() wird eine Referenz gestohlen. Ersteres kann also aus welchem Grund auch immer fehlschlagen (zum Beispiel weil die Liste nicht verlängert werden kann), zweiteres kann nie fehlschlagen, weil der Platz schon vorallokiert ist, deswegen ist es auch ein Makro, während ersteres eine Funktion ist.

Wenn Du umbedingt Listen brauchst kann ich auch Code dafür machen, der sieht aber dann nicht mehr ganz so schön kompakt aus. ;-)
Hier zeigt sich, das der Referenzzähler des PyData vor dem XDECREF auf 1 steht und danach 14731456 aufweist. Wodurch ist das zu erklären?
Wenn der Referenzzähler vorher auf 1 war ist das Objekt nach dem Py_XDECREF unbrauchbar (also freigegeben). Deswegen ist auch der Referenzzähler nicht mehr zu gebrauchen (bzw. der Inhalt unsinnig, und nicht notwendigerweise 0).
--- Heiko.
Benutzeravatar
windows97
User
Beiträge: 24
Registriert: Freitag 10. März 2006, 13:34

Dass das bei PyList_Append() auftaucht ist mir vollkommen einleutend gewesen.
Mein Prob liegt darin, das du nen neuen Tuple erzeugen kannst

Code: Alles auswählen

if( !( PyARB_Point = PyTuple_New(0) ) ) goto end_error; 
und dass dieses(?) Tuple dann allokierten Speicher besitzt.
PyList_New(0) aber ja anscheinend nicht...
Wenn Du umbedingt Listen brauchst kann ich auch Code dafür machen, der sieht aber dann nicht mehr ganz so schön kompakt aus. Wink
Untersteh dich! Ich wills selber verstehen. Copy-Paste bringt mir da nicht so wirklich viel. - Aber Danke! :)

Das mit dem Referenzzähler leuchtet ein.
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Mein Prob liegt darin, das du nen neuen Tuple erzeugen kannst
Das war nicht ganz richtig was ich da geschrieben hab, das ist mir aber erst jetzt aufgefallen, und es ist erstaunlich dass es anscheinend trotzdem lief, oder?

Auf jeden Fall sollte das sein:

Code: Alles auswählen

if( !( PyARB_Point = PyTuple_New(3) ) ) goto end_error;
Bemerke die 3, die sagt eben dass das Tupel drei Elemente hat, damit ist die Tupelgröße voralloziert, und ändert sich auch nie mehr. Mich wunderts dass es mit 0 (was ich aus Deinem PyList_New(0) übernommen hatte) geht; weil Du im Endeffekt hinter das Array schreibst was die Zeiger auf die Tupelelemente enthält (PyTuple_SET_ITEM ist nämlich nichts anderes als ein einfacher Index-Operator versteckt in einem Makro), und deswegen eigentlich das Programm nicht gehen sollte. Aber, okay, das hat wahrscheinlich was mit der Heap-Überallokation von Python zu tun, so dass Speicher nach dem Array auch schon allokiert ist, aber nicht benutzt war, usw.

Bei Listen stellt man mit dem entsprechenden Parameter ein vieviele Elemente vorallokiert sind (also wie groß das Array am Anfang _mindestens_ sein soll, meißtens ist es größer, weil Python bestimmte Schritte verwendet). Wenn Du dann einen PyList_Append machst und vorher schon mit Größe drei vorallokiert hast wird PyList_Append eigentlich nie fehlschlagen können, weil die Liste ja schon besteht. Dafür müßtest Du Dir aber den Quellcode noch mal genauer angucken. Zumindest würde mir da auch nix einfallen was fehlschlagen könnte.

Auf jeden Fall stiehlt PyList_Append die Referenz eben nicht (soweit ich weiß, wohlgemerkt, aber ich hab gerade im Handbuch nachgeguckt und da steht nix gegenteiliges), während PyTuple_SET_ITEM das sehr wohl macht. Dementsprechend mußt Du wenn Du ersteres benutzt das Element was Du in die Liste einträgst Py_DECREF()-fen, damit nur noch die Referenz aus der Liste draufzeigt, und keine "dangling reference" besteht die vergessen wird wenn Deine Methode sich beendet.
--- Heiko.
Antworten