web-dev-qa-db-de.com

Wie rufen Sie Python-Code aus C-Code auf?

Ich möchte ein großes C-Projekt um einige neue Funktionen erweitern, aber ich möchte es wirklich in Python schreiben. Grundsätzlich möchte ich Python-Code aus C-Code aufrufen. Python-> C-Wrapper wie SWIG ermöglichen jedoch das OPPOSITE, dh das Schreiben von C-Modulen und das Aufrufen von C aus Python.

Ich denke über einen Ansatz nach IPC oder RPC nach (ich habe nichts dagegen, mehrere Prozesse zu haben). Das heißt, dass meine pure-Python-Komponente in einem separaten Prozess (auf derselben Maschine) ausgeführt wird und dass mein C-Projekt durch Schreiben/Lesen aus einem Socket (oder einer Unix-Pipe) mit ihm kommuniziert. meine Python-Komponente kann auf Sockets lesen/schreiben, um zu kommunizieren. Ist das ein vernünftiger Ansatz? Gibt es etwas Besseres? Wie ein spezieller RPC-Mechanismus?

Danke für die bisherige Antwort - Ich möchte mich jedoch auf IPC-basierte Ansätze konzentrieren, da ich mein Python-Programm in einem separaten Prozess als C-Programm verwenden möchte. Ich möchte keinen Python-Interpreter einbetten. Vielen Dank!

34
pgb

Ich empfehle die hier beschriebenen Ansätze . Zunächst wird erklärt, wie Strings aus Python-Code ausgeführt werden. Anschließend wird beschrieben, wie eine Python-Umgebung für die Interaktion mit Ihrem C-Programm eingerichtet, Python-Funktionen aus Ihrem C-Code aufgerufen, Python-Objekte aus C-Code bearbeitet werden usw.

BEARBEITEN: Wenn Sie wirklich den Weg des IPC gehen wollen, sollten Sie das struct-Modul oder noch besser protlib verwenden. Die meiste Kommunikation zwischen einem Python- und einem C-Prozess besteht darin, Strukturen vor- und zurückzubewegen, entweder über einen Socket oder durch Shared Memory .

Ich empfehle, eine Command struct mit Feldern und Codes zu erstellen, um Befehle und ihre Argumente darzustellen. Ich kann nicht viel spezifischere Ratschläge erteilen, ohne mehr darüber zu wissen, was Sie erreichen möchten, aber generell empfehle ich die Bibliothek protlib , da ich damit zwischen C- und Python-Programmen kommuniziere (Disclaimer: Autor von Protlib).

11
Eli Courtwright

Siehe das entsprechende Kapitel im Handbuch: http://docs.python.org/extending/

Im Wesentlichen müssen Sie den Python-Interpreter in Ihr Programm einbetten.

4
janneb

Haben Sie darüber nachgedacht, Ihre Python-Anwendung einfach in ein Shell-Skript zu packen und von dort aus mit Ihrer C-Anwendung aufzurufen?

Nicht die eleganteste Lösung, aber sie ist sehr einfach.

4
hhafez

Ich habe keinen IPC -Ansatz für die Kommunikation mit Python <-> C verwendet, aber es sollte ziemlich gut funktionieren. Ich würde das C-Programm einen Standard-Fork-Exec ausführen und die umgeleiteten stdin und stdout im Child-Prozess für die Kommunikation verwenden. Eine Textkommunikation in Nizza macht es sehr einfach, das Python-Programm zu entwickeln und zu testen.

1
D.Shawley

Wenn ich mich für IPC entschieden hätte, würde ich wahrscheinlich mit XML-RPC - plattformübergreifend plurge sein. Sie können das Python-Serverprojekt später problemlos auf einen anderen Knoten setzen, wenn Sie möchten, hat viele ausgezeichnete Implementierungen ( Siehe hier für viele, einschließlich C und Python und hier für den einfachen XML-RPC-Server, der Teil der Python-Standardbibliothek ist - nicht so hoch skalierbar wie andere Ansätze, aber wahrscheinlich gut und bequem für Sie Anwendungsfall).

Es ist nicht unbedingt ein perfekter IPC -Ansatz für alle Fälle (oder auf jeden Fall ein perfekter RPC-Fall!), Aber die Bequemlichkeit, Flexibilität, Robustheit und ein breites Spektrum an Implementierungen überwiegen viele kleinere Mängel meine Meinung.

1
Alex Martelli

Ich habe den "Standard" -Ansatz von Embedding Python in Another Application verwendet. Aber es ist kompliziert/langweilig. Jede neue Funktion in Python ist schmerzhaft zu implementieren.

Ich habe ein Beispiel von Calling PyPy von C gesehen. Es verwendet CFFI, um die Schnittstelle zu vereinfachen, erfordert jedoch PyPy, nicht Python. Lesen und verstehen Sie dieses Beispiel zuerst, zumindest auf hohem Niveau.

Ich habe das C/PyPy-Beispiel geändert, um mit Python zu arbeiten. So rufen Sie Python von C aus mit CFFI auf. 

Mein Beispiel ist komplizierter, da ich statt einer Funktion drei Funktionen in Python implementiert habe. Ich wollte weitere Aspekte der Hin- und Rückübertragung von Daten behandeln. 

Der komplizierte Teil ist jetzt isoliert, um die Adresse von api an Python zu übergeben. Das muss nur einmal umgesetzt werden. Danach können Sie problemlos neue Funktionen in Python hinzufügen.

interface.h

// These are the three functions that I implemented in Python.
// Any additional function would be added here.
struct API {
    double (*add_numbers)(double x, double y);
    char* (*dump_buffer)(char *buffer, int buffer_size);
    int (*release_object)(char *obj);
};

test_cffi.c

//
// Calling Python from C.
// Based on Calling PyPy from C:
// http://doc.pypy.org/en/latest/embedding.html#more-complete-example
//

#include <stdio.h>
#include <assert.h>

#include "Python.h"

#include "interface.h"

struct API api;   /* global var */

int main(int argc, char *argv[])
{
    int rc;

    // Start Python interpreter and initialize "api" in interface.py using 
    // old style "Embedding Python in Another Application":
    // https://docs.python.org/2/extending/embedding.html#embedding-python-in-another-application
    PyObject *pName, *pModule, *py_results;
    PyObject *fill_api;
#define PYVERIFY(exp) if ((exp) == 0) { fprintf(stderr, "%s[%d]: ", __FILE__, __LINE__); PyErr_Print(); exit(1); }

    Py_SetProgramName(argv[0]);  /* optional but recommended */
    Py_Initialize();
    PyRun_SimpleString(
            "import sys;"
            "sys.path.insert(0, '.')" );

    PYVERIFY( pName = PyString_FromString("interface") )
    PYVERIFY( pModule = PyImport_Import(pName) )
    Py_DECREF(pName);
    PYVERIFY( fill_api = PyObject_GetAttrString(pModule, "fill_api") )

    // "k" = [unsigned long],
    // see https://docs.python.org/2/c-api/arg.html#c.Py_BuildValue
    PYVERIFY( py_results = PyObject_CallFunction(fill_api, "k", &api) )
    assert(py_results == Py_None);

    // Call Python function from C using cffi.
    printf("sum: %f\n", api.add_numbers(12.3, 45.6));

    // More complex example.
    char buffer[20];
    char * result = api.dump_buffer(buffer, sizeof buffer);
    assert(result != 0);
    printf("buffer: %s\n", result);

    // Let Python perform garbage collection on result now.
    rc = api.release_object(result);
    assert(rc == 0);

    // Close Python interpreter.
    Py_Finalize();

    return 0;
}

interface.py

import cffi
import sys
import traceback

ffi = cffi.FFI()
ffi.cdef(file('interface.h').read())

# Hold references to objects to prevent garbage collection.
noGCDict = {}

# Add two numbers.
# This function was copied from the PyPy example.
@ffi.callback("double (double, double)")
def add_numbers(x, y):
    return x + y

# Convert input buffer to repr(buffer).
@ffi.callback("char *(char*, int)")
def dump_buffer(buffer, buffer_len):
    try:
        # First attempt to access data in buffer.
        # Using the ffi/lib objects:
        # http://cffi.readthedocs.org/en/latest/using.html#using-the-ffi-lib-objects
        # One char at time, Looks inefficient.
        #data = ''.join([buffer[i] for i in xrange(buffer_len)])

        # Second attempt.
        # FFI Interface:
        # http://cffi.readthedocs.org/en/latest/using.html#ffi-interface
        # Works but doc says "str() gives inconsistent results".
        #data = str( ffi.buffer(buffer, buffer_len) )

        # Convert C buffer to Python str.
        # Doc says [:] is recommended instead of str().
        data = ffi.buffer(buffer, buffer_len)[:]

        # The goal is to return repr(data)
        # but it has to be converted to a C buffer.
        result = ffi.new('char []', repr(data))

        # Save reference to data so it's not freed until released by C program.
        noGCDict[ffi.addressof(result)] = result

        return result
    except:
        print >>sys.stderr, traceback.format_exc()
        return ffi.NULL

# Release object so that Python can reclaim the memory.
@ffi.callback("int (char*)")
def release_object(ptr):
    try:
        del noGCDict[ptr]
        return 0
    except:
        print >>sys.stderr, traceback.format_exc()
        return 1

def fill_api(ptr):
    global api
    api = ffi.cast("struct API*", ptr)

    api.add_numbers = add_numbers
    api.dump_buffer = dump_buffer
    api.release_object = release_object

Kompilieren:

gcc -o test_cffi test_cffi.c -I/home/jmudd/pgsql-native/Python-2.7.10.install/include/python2.7 -L/home/jmudd/pgsql-native/Python-2.7.10.install/lib -lpython2.7

Ausführen:

$ test_cffi
sum: 57.900000
buffer: 'T\x9e\x04\x08\xa8\x93\xff\xbf]\x86\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00'
$ 
0
JohnMudd

anscheinend muss Python in Win32-DLL kompilieren können, es wird das Problem lösen

Durch die Konvertierung von c # -Code in win32-DLLs kann es von jedem Entwicklungswerkzeug verwendet werden

0
Mandrake

Das scheint ganz nett zu sein http://thrift.Apache.org/ , es gibt sogar ein Buch darüber.

Einzelheiten:

Das Apache Thrift-Software-Framework für skalierbare sprachenübergreifende Entwicklung von Services, kombiniert einen Software-Stack mit einer Codegenerierung Engine, um Dienste zu erstellen, die effizient und nahtlos zwischen .__ arbeiten. C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C #, Kakao, JavaScript, Node.js, Smalltalk, OCaml und Delphi und andere Sprachen.

0
jhegedus