Liste/Array in C erzeugen (zur Übergabe an Python)

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
Casisto
User
Beiträge: 12
Registriert: Sonntag 20. Oktober 2013, 13:11

Hallo Forum,

meine Frage bezieht sich diesmal stark auf Py_BuildValue.

Ist es irgendwie möglich drei 1D-Array oder ein 2D-Array aus C mit, vor Laufzeit, unbekannter Länge, an Python zu übergeben?
(Also Y-Dimension wäre bei den 2D-Array dann natürlich 3; die X-Dimension wäre auch immer gleich lang)

meine bisherige Lösung gefällt mir gar nicht, bzw. geht auch nur mit fester Länge:

Das C-Programm:

Code: Alles auswählen

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "python2.7/Python.h"

int main()
{
	srand(time(NULL));
	int i;
	
	int a[5], b[5], c[5];
	
	for(i=0; i < 5; i++)
	{
		a[i] = rand() % 10+10;	//10-19
		b[i] = rand() % 5+5;	//5-9
		c[i] = rand() % 5;		//-4
		printf("%d  ", a[i]);
		printf("%d  ", b[i]);
		printf("%d\n", c[i]);
	}
	
	Py_Initialize();
	PyObject *modul, *funk, *ret, *zahl;
	
	PySys_SetPath(".");
	modul = PyImport_ImportModule("script2"); 
	
	if(modul)
	{
		funk = PyObject_GetAttrString(modul, "ausgabe");
		
		//Werte einzeln übergebn...
		zahl = Py_BuildValue("(iiiii iiiii iiiii)",a[0], a[1], a[2], a[3], a[4],
			   b[0], b[1], b[2], b[3], b[4],
			   c[0], c[1], c[2], c[3], c[4]);

		ret = PyObject_CallObject(funk, zahl);

		Py_DECREF(zahl);	
		Py_DECREF(ret);	
		Py_DECREF(funk);
		Py_DECREF(modul);			
	}
	else
		printf("Fehler: Modul nicht gefunden\n");
	
	return 0;
}
Das Python-Skript:

Code: Alles auswählen

def ausgabe(a0, a1, a2, a3, a4, b0, b1, b2, b3, b4, c0, c1, c2, c3, c4):
	print "a = [", a0, ", ", a1, ", ", a2,  ", ", a3,  ", ", a4, "]"
	print "b = [", b0, ", ", b1, ", ", b2,  ", ", b3,  ", ", b4, "]"
	print "c = [", c0, ", ", c1, ", ", c2,  ", ", c3,  ", ", c4, "]"
Ich danke schonmal im Voraus für eure Hilfe
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@Casisto: wenn Du eine Liste variabler Länge hast (oder irgendeine andere Liste), mußt Du schon mit PyList_New ein List-Objekt erzeugen, das Du dann mit Werten füllen kannst. Py_BuildValue ist dafür nicht geeignet (höchstens um die Dripple für die Listeinträge zu erzeugen).
BlackJack

@Casisto: `Py_BuildValue()` ist da wohl eher nicht das Mittel der Wahl. Man würde hier wohl eher auf C-Seite Python-Objekte erstellen. Schau Dir mal Buffers and Memoryview Objects an.

Edit: Die Fehlerbehandlung könnte übrigens besser sein. Du prüfst kaum Rückgabewerte und bei dem einzigen den Du prüfst, gibst Du eine Meldung aus, die nicht zum tatsächlichen Fehler passen muss. Es kann ja sehr wohl sein, dass das Modul gefunden wurde, aber einen Syntaxfehler enthält, oder beim Ausführen des Codes auf Modulebene irgendein Laufzeitfehler aufgetreten ist.
Casisto
User
Beiträge: 12
Registriert: Sonntag 20. Oktober 2013, 13:11

Ok Danke erstmal,

habt ihr eventuell ein kleines Beispiel für die Lösungsansätze parat? eine Googlesuche hat mir leider nicht viel weiter geholfen.
BlackJack

@Casisto: Da wird es wohl kein dediziertes Tutorial für geben, das ist ja auch eher trivial wenn man die Grundlagen der Python/C-API verstanden hat. Man muss sich halt aus der Python/C-API-Dokumentation die passenden Funktionen raussuchen um Listen und `int`-Objekte zu erstellen und die `int`-Objekte dann in die jeweilige Liste zu stellen.

Code: Alles auswählen

#include "Python.h"
#include <stdint.h>

#define N   5
#define LIST_COUNT  3

/* Return value: new reference */
PyObject* create_list_from_array(int *array, size_t length)
{
    size_t i;
    PyObject *result = NULL, *value = NULL;

    result = PyList_New(length);
    if (result) {
        for (i = 0; i < length; ++i) {
            value = PyInt_FromLong(array[i]);
            if (value) {
                PyList_SET_ITEM(result, i, value);
            } else {
                Py_CLEAR(result);
                break;
            }
        }
    }
    return result;
}

int main(void)
{
    int i;
    int a[N], b[N], c[N];
    int *arrays[] = {a, b, c};
    PyObject *module = NULL;
    PyObject *lists[LIST_COUNT], *args = NULL, *callable = NULL, *result = NULL;

    for (i = 0; i < LIST_COUNT; ++i) lists[i] = NULL;
    
    for (i = 0; i < N; ++i) {
        a[i] = i;
        b[i] = -i;
        c[i] = 10 * i;
    }
    
    Py_Initialize();

    /* XXX Bad idea.  Only modules built into the interpreter and from the
         current working directory can be used.  This even excludes most of
         the standard library. */
    PySys_SetPath(".");

    module = PyImport_ImportModule("test");
    if (!module) goto error;

    for (i = 0; i < LIST_COUNT; ++i) {
        lists[i] = create_list_from_array(arrays[i], N);
        if (!lists[i]) goto error;
    }

    args = Py_BuildValue("(SSS)", lists[0], lists[1], lists[2]);
    if (!args) goto error;

    callable = PyObject_GetAttrString(module, "ausgabe");
    if (!callable) goto error;

    result = PyObject_CallObject(callable, args);
    if (!result) goto error;

error:
    if (PyErr_Occurred()) PyErr_Print();

    Py_XDECREF(result);
    Py_XDECREF(callable);
    Py_XDECREF(args);
    Py_XDECREF(module);

    Py_Finalize();

    return 0;
}
Und das Python-Modul `test`:

Code: Alles auswählen

def ausgabe(a, b, c):
    print 'a =', a
    print 'b =', b
    print 'c =', c
Casisto
User
Beiträge: 12
Registriert: Sonntag 20. Oktober 2013, 13:11

Hi, noch eine letzte Frage (hoffe ich). Und zwar die Codezeile

Code: Alles auswählen

PySys_SetPath(".");
habe ich versucht durch

Code: Alles auswählen

	PyObject *sys = PyImport_ImportModule("sys");
	PyObject *path = PyObject_GetAttrString(sys, "path");
	PyList_Append(path, PyString_FromString("."));
zu ersetzen um somit auch Module von Python benutzen zu können.

Leider kommt dann die Fehlermeldung:
AttributeError: 'module' object has no attribute 'ausgabe'
Kann mir nochmal jemand Hilfestellung geben?
BlackJack

@Casisto: Wenn Du den '.' hinten anhängst, dann kommt sehr wahrscheinlich ein anderes `test`-Modul in einem der anderen Pfade vor. Den '.' sollte man vorne einfügen.
Casisto
User
Beiträge: 12
Registriert: Sonntag 20. Oktober 2013, 13:11

Hallo,

nur um später Suchenden meine Lösung zu präsentieren:

Code: Alles auswählen

	PyObject *sys = PyImport_ImportModule("sys");
	PyObject *path = PyObject_GetAttrString(sys, "path");
	PyList_Reverse(path);
	PyList_Append(path, PyString_FromString("."));
 	PyList_Reverse(path);
Falls jemand eine elegantere Lösung als zweimal "Reverse" zu bemühen hat, gern her damit.
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@Casisto: so wie man es in Python auch machen würde: "sys.path.insert" bzw. PyList_Insert.
Casisto
User
Beiträge: 12
Registriert: Sonntag 20. Oktober 2013, 13:11

@Sirius3:
PyList_Insert habe ich probiert, erzeugt bei mir aber nur ein "Segmentation Fault". sys.path.insert kenne ich ehrlich gesagt gar nicht.
BlackJack

@Casisto: Dann machst Du bei `PyList_insert()` irgend etwas falsch. Bei den fünf Zeilen mit dem `PyList_reverse()` sehe ich zum Beispiel keine Fehlerbehandlung. Wenn irgendwo ein Fehler auftritt wird das bei den meisten Funktionen der API durch einen NULL-Zeiger angezeigt, und die Funktionen gehen davon aus das man nirgends NULL übergibt wo das nicht ausdrücklich erlaubt ist. Man handelt sich also ganz schnell solche Abstürze ein wenn man nicht ordentlich prüft ob die einzelnen Aufrufe auch erfolgreich waren.

`sys.path` ist eine Liste und Listen haben eine `insert()`-Methode. Da gibt es also nichts spezielles zu kennen.

Edit:

Code: Alles auswählen

int insert_current_dir_into_sys_path(void)
{
    int result = -1;
    PyObject *sys_path, *current_dir;

    if ((sys_path = PySys_GetObject("path"))) {
        if ((current_dir = PyString_FromString("."))) {
            result = PyList_Insert(sys_path, 0, current_dir);
            Py_DECREF(current_dir);
        }
    }
    return result;
}
Antworten