Erstellen und rufen Sie eine Python-Funktion aus einer Zeichenfolge über die C-API auf

Lesezeit: 7 Minuten

Benutzeravatar von alanc10n
alanc10n

Ist es möglich, eine Python-Funktion aus einer Zeichenfolge zu laden und diese Funktion dann mit Argumenten aufzurufen und den Rückgabewert zu erhalten?

Ich verwende die Python-C-API, um Python-Code in meiner C++-Anwendung auszuführen. Ich kann ein Modul aus einer Datei laden mit PyImport_Importerhalten Sie ein Funktionsobjekt davon mit PyObject_GetAttrStringund rufen Sie die Funktion mit auf PyObject_CallObject. Ich möchte das Modul/die Funktion aus einer Zeichenfolge anstelle einer Datei laden. Gibt es etwas Äquivalent zu PyImport_Import was würde es mir ermöglichen, eine Zeichenfolge anstelle einer Datei zu übergeben? Ich muss Argumente an die Funktion übergeben, die ich aufrufe, und ich brauche Zugriff auf den Rückgabewert, also kann ich nicht einfach verwenden PyRun_SimpleString.


Bearbeiten:

Ich fand diese Lösung, nachdem ich eingeschaltet wurde PyRun_String. Ich erstelle ein neues Modul, erhalte sein Wörterbuchobjekt und übergebe es in einem Aufruf an PyRun_String um eine Funktion in meinem neuen Modul zu definieren, dann ein Funktionsobjekt für diese neu erstellte Funktion zu erhalten und es über aufzurufen PyObject_CallObject, meine Argumente übergeben. Folgendes habe ich gefunden, um mein Problem zu lösen:
main.cpp


int main()
{
    PyObject *pName, *pModule, *pArgs, *pValue, *pFunc;
    PyObject *pGlobal = PyDict_New();
    PyObject *pLocal;

    //Create a new module object
    PyObject *pNewMod = PyModule_New("mymod");

    Py_Initialize();
    PyModule_AddStringConstant(pNewMod, "__file__", "");

    //Get the dictionary object from my module so I can pass this to PyRun_String
    pLocal = PyModule_GetDict(pNewMod);

    //Define my function in the newly created module
    pValue = PyRun_String("def blah(x):\n\tprint 5 * x\n\treturn 77\n", Py_file_input, pGlobal, pLocal);
    Py_DECREF(pValue);

    //Get a pointer to the function I just defined
    pFunc = PyObject_GetAttrString(pNewMod, "blah");

    //Build a tuple to hold my arguments (just the number 4 in this case)
    pArgs = PyTuple_New(1);
    pValue = PyInt_FromLong(4);
    PyTuple_SetItem(pArgs, 0, pValue);

    //Call my function, passing it the number four
    pValue = PyObject_CallObject(pFunc, pArgs);
    Py_DECREF(pArgs);
    printf("Returned val: %ld\n", PyInt_AsLong(pValue));
    Py_DECREF(pValue);

    Py_XDECREF(pFunc);
    Py_DECREF(pNewMod);
    Py_Finalize();

    return 0;
}

Hier ist der Rest meines ursprünglichen Beitrags, der der Nachwelt überlassen wurde:

Folgendes habe ich ursprünglich gemacht:
main.cpp:


#include <Python.h>

int main()
{
    PyObject *pName, *pModule, *pArgs, *pValue, *pFunc;

    Py_Initialize();
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('')");
    pName = PyString_FromString("atest");
    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if(pModule == NULL)
    {
        printf("PMod is null\n");
        PyErr_Print();
        return 1;
    }

    pFunc = PyObject_GetAttrString(pModule, "doStuff");
    pArgs = PyTuple_New(1);
    pValue = PyInt_FromLong(4);
    PyTuple_SetItem(pArgs, 0, pValue);

    pValue = PyObject_CallObject(pFunc, pArgs);
    Py_DECREF(pArgs);
    printf("Returned val: %ld\n", PyInt_AsLong(pValue));
    Py_DECREF(pValue);

    Py_XDECREF(pFunc);
    Py_DECREF(pModule);

    Py_Finalize();

    return 0;
}

Und atest.py:


def doStuff( x):
    print "X is %d\n" % x
    return 2 * x

  • Es ist nicht notwendig, Ihren Posten der Nachwelt zu hinterlassen. Stack Overflow hat das bereits abgedeckt. Alle Änderungen an Fragen und Antworten werden automatisch archiviert, was wir sehen können, sobald unsere Punkte ein bestimmtes Niveau erreichen. Das hilft uns, fehlerhafte Änderungen rückgängig zu machen und die Änderungen nachzuverfolgen, um Fragen besser zu verstehen. Anstatt es dort zu belassen, können Sie also Ihre Änderungen löschen und wir können uns bei Bedarf das Bearbeitungsprotokoll ansehen. Aus diesem Grund ist es nicht einmal notwendig, “Edit:” einzugeben.

    – der Blechmann

    14. Januar 2015 um 20:02 Uhr


  • Für andere, die darüber stolpern: Im ersten Codeschnipsel wird Py_Initialize zu spät aufgerufen. nur dass du es weißt

    – Hallo Welt

    3. April 2018 um 16:25 Uhr


  • Überprüfen Sie, ob Sie pValue zweimal verwenden, die erste Referenz muss ein Speicherleck verursachen

    – themadmax

    8. August 2018 um 9:33 Uhr

  • eine Frage: Die in einem einzelnen PyRun_String-Aufruf definierte Funktion muss vollständig in einem einzigen Aufruf enthalten sein? dh der Hauptteil der Funktion kann nicht in aufeinanderfolgende Aufrufe von PyRun_String aufgeteilt werden?

    – Benutzer3181125

    4. November um 18:16 Uhr

PyRun_String in der Python-C-API ist wahrscheinlich das, wonach Sie suchen. Sehen: http://docs.python.org/c-api/veryhigh.html

  • Es ist nicht klar, wie ich Argumente an die Funktion übergeben würde. Es sieht so aus, als könnte ich eine Funktion mit definieren PyRun_String, aber ich sehe nicht, wie ich es mit Parametern aufrufen soll, die nicht nur eine hartcodierte Zeichenfolge sind. Das heißt, ich könnte PyRun_String erneut mit “doStuff(7)” als erstem Argument aufrufen, aber das funktioniert nicht, wenn das Argument ein Objekt oder so etwas ist. Gibt es eine Möglichkeit, PyObject_GetAttrString aufzurufen, um das Funktionsobjekt für doStuff abzurufen, wenn es über PyRun_String definiert wurde?

    – alanc10n

    24. September 2010 um 20:57 Uhr

  • Akzeptieren, weil es mich auf den Weg gebracht hat, eine Lösung zu finden, obwohl ich a tun musste viel zu lesen, um dorthin zu gelangen. Vielen Dank.

    – alanc10n

    24. September 2010 um 21:55 Uhr

  • Tut mir leid, ich war die letzten paar Stunden nicht online, aber es sieht so aus, als hättest du es genau herausgefunden. PyRun_String führt einen Block von Python-Code aus einer Zeichenfolge aus und gibt zurück, was auch immer der Code zurückgegeben hätte – ich nehme an, das ist es None in Ihrem Fall. Danach wird die Funktion, die Sie gerade ausgewertet haben, dem lokalen Namensraum hinzugefügt – Sie müssen sie von dort mit abrufen PyObject_GetAttrString und dann anrufen. Vielleicht möchten Sie auch den Rückgabewert von überprüfen PyRun_String – wenn es ist NULList beim Ausführen des Codeblocks eine Ausnahme aufgetreten.

    – Tamás

    24. September 2010 um 22:33 Uhr

Die in der Frage enthaltene Antwort ist ausgezeichnet, aber ich hatte einige kleine Probleme damit Python 3.5um also zu verhindern, dass andere das tun, was ich getan habe, ist unten eine leicht bearbeitete Version, die zumindest für diese Version von Python gut zu funktionieren scheint:

#include <Python.h>

int main(void)
{
    PyObject *pArgs, *pValue, *pFunc, *pModule, *pGlobal, *pLocal;

    Py_Initialize();

    pGlobal = PyDict_New();

    //Create a new module object
    pModule = PyModule_New("mymod");
    PyModule_AddStringConstant(pModule, "__file__", "");

    //Get the dictionary object from my module so I can pass this to PyRun_String
    pLocal = PyModule_GetDict(pModule);

    //Define my function in the newly created module
    pValue = PyRun_String("def blah(x):\n\ty = x * 5\n\treturn y\n",
        Py_file_input, pGlobal, pLocal);

    //pValue would be null if the Python syntax is wrong, for example
    if (pValue == NULL) {
        if (PyErr_Occurred()) {
            PyErr_Print();
        }
        return 1;
    }

    //pValue is the result of the executing code, 
    //chuck it away because we've only declared a function
    Py_DECREF(pValue);

    //Get a pointer to the function I just defined
    pFunc = PyObject_GetAttrString(pModule, "blah");

    //Double check we have actually found it and it is callable
    if (!pFunc || !PyCallable_Check(pFunc)) {
        if (PyErr_Occurred()) {
            PyErr_Print();
        }
        fprintf(stderr, "Cannot find function \"blah\"\n");
        return 2;
    }

    //Build a tuple to hold my arguments (just the number 4 in this case)
    pArgs = PyTuple_New(1);
    pValue = PyLong_FromLong(4);
    PyTuple_SetItem(pArgs, 0, pValue);

    //Call my function, passing it the number four
    pValue = PyObject_CallObject(pFunc, pArgs);

    fprintf(stdout, "Returned value: %ld\n", PyLong_AsLong(pValue));

    Py_DECREF(pValue);
    Py_XDECREF(pFunc);

    Py_Finalize();

    return 0;
}

  • Hervorragende Antwort! Sehr nützlich für mich. Ich habe viele Teile dieses Codes in meinem Programm verwendet.

    – Arti

    13. Januar 2021 um 17:53 Uhr


Benutzeravatar von coolingrator
Kühlschrank

Diese Frage ist ziemlich alt, aber wenn jemand immer noch versucht, dies herauszufinden (wie ich), werde ich meine Antwort geben.

Die ursprüngliche Antwort und die aktualisierte Antwort von @kmp funktionieren für einfache Beispiele, wenn jedoch so etwas wie eine import -Anweisung in den Code eingeführt wird, funktioniert dies nicht.

Ich habe hier ein funktionierendes Beispiel gefunden: https://awasu.com/weblog/embedding-python/writing-ac-wrapper-library-part3/

Kurz gesagt (mit Python 3.6 API):

  • Lesen Sie eine Python-Datei in:
std::string line, text;
std::ifstream in("the_file.py");
while (std::getline(in, line)) {
  text += line + "\n";
}
const char *data = text.c_str();
  • Kompilieren Sie es zu Bytecode:
PyObject *pCodeObj = Py_CompileString(data, "", Py_file_input);
  • Erstellen Sie ein Modul, indem Sie den Code ausführen:
pModule = PyImport_ExecCodeModule("the_module", pCodeObj );

Sie können dann fortfahren, indem Sie Funktionen mit abrufen PyObject_GetAttrString und sie wie in den obigen Beispielen aufrufen.

1432310cookie-checkErstellen und rufen Sie eine Python-Funktion aus einer Zeichenfolge über die C-API auf

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy