1. Bädda in Python i en annan applikation

I de föregående kapitlen diskuterades hur man utökar Python, det vill säga hur man utökar Pythons funktionalitet genom att koppla ett bibliotek med C-funktioner till det. Det är också möjligt att göra tvärtom: berika din C/C++-applikation genom att bädda in Python i den. Inbäddning ger din applikation möjlighet att implementera en del av funktionaliteten i din applikation i Python snarare än i C eller C++. Detta kan användas för många ändamål; ett exempel skulle kunna vara att låta användarna skräddarsy applikationen efter sina behov genom att skriva några skript i Python. Du kan också använda det själv om en del av funktionaliteten lättare kan skrivas i Python.

Att bädda in Python liknar att utöka det, men inte riktigt. Skillnaden är att när du utökar Python är applikationens huvudprogram fortfarande Python-tolken, medan om du bäddar in Python kanske huvudprogrammet inte har något att göra med Python — istället kallar vissa delar av applikationen ibland Python-tolken för att köra lite Python-kod.

Så om du bäddar in Python tillhandahåller du ditt eget huvudprogram. En av de saker som detta huvudprogram måste göra är att initiera Python-tolken. Åtminstone måste du anropa funktionen Py_Initialize(). Det finns valfria anrop för att skicka kommandoradsargument till Python. Senare kan du anropa tolken från vilken del som helst av programmet.

Det finns flera olika sätt att anropa tolken: du kan skicka en sträng som innehåller Python-satser till PyRun_SimpleString(), eller så kan du skicka en stdio-filpekare och ett filnamn (endast för identifiering i felmeddelanden) till PyRun_SimpleFile(). Du kan också anropa de operationer på lägre nivå som beskrivs i de föregående kapitlen för att konstruera och använda Python-objekt.

Se även

Python/C API Referensmanual

Detaljerna i Pythons C-gränssnitt beskrivs i den här handboken. En stor del av den nödvändiga informationen finns här.

1.1. Inbäddning på mycket hög nivå

Den enklaste formen av Python-inbäddning är att använda ett gränssnitt på mycket hög nivå. Detta gränssnitt är avsett att exekvera ett Python-skript utan att behöva interagera med applikationen direkt. Detta kan t.ex. användas för att utföra någon operation på en fil:

#definiera PY_SSIZE_T_CLEAN
#inkludera <Python.h>

int
main(int argc, char *argv[])
{
    PyStatus status;
    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    /* valfritt men rekommenderas */
    status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
    if (PyStatus_Exception(status)) {
        goto undantag;
    }

    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
         till undantaget;
    }
    PyConfig_Clear(&config);

    PyRun_SimpleString("from time import time,ctime\n"
                       "print('Idag är', ctime(time()))\n");
    if (Py_FinalizeEx() < 0) {
        exit(120);
    }
    return 0;

  undantag:
     PyConfig_Clear(&config);
     Py_ExitStatusException(status);
}

Anteckning

#define PY_SSIZE_T_CLEAN användes för att indikera att Py_ssize_t skulle användas i vissa API:er istället för int. Det är inte nödvändigt sedan Python 3.13, men vi behåller det här för bakåtkompatibilitet. Se Strängar och buffertar för en beskrivning av detta makro.

Inställningen PyConfig.program_name bör anropas före Py_InitializeFromConfig() för att informera tolken om sökvägar till Pythons run-time-bibliotek. Därefter initieras Python-tolken med Py_Initialize(), följt av exekveringen av ett hårdkodat Python-skript som skriver ut datum och tid. Därefter stänger anropet Py_FinalizeEx() av tolken, följt av slutet på programmet. I ett riktigt program kanske du vill hämta Python-skriptet från en annan källa, kanske en textredigeringsrutin, en fil eller en databas. Det är lättare att hämta Python-koden från en fil genom att använda funktionen PyRun_SimpleFile(), som sparar dig besväret med att allokera minnesutrymme och ladda filens innehåll.

1.2. Bortom inbäddning på mycket hög nivå: En översikt

Högnivågränssnittet ger dig möjlighet att exekvera godtyckliga bitar av Python-kod från din applikation, men att utbyta datavärden är minst sagt besvärligt. Om du vill ha det bör du använda anrop på lägre nivå. På bekostnad av att behöva skriva mer C-kod kan du uppnå nästan vad som helst.

Det bör noteras att utöka Python och bädda in Python är ganska samma aktivitet, trots den olika avsikten. De flesta ämnen som diskuterats i de tidigare kapitlen är fortfarande giltiga. För att visa detta kan du fundera på vad tilläggskoden från Python till C verkligen gör:

  1. Konvertera datavärden från Python till C,

  2. Utföra ett funktionsanrop till en C-rutin med hjälp av de konverterade värdena, och

  3. Konvertera datavärdena från anropet från C till Python.

Vid inbäddning av Python gör gränssnittskoden det:

  1. Konvertera datavärden från C till Python,

  2. Utföra ett funktionsanrop till en Python-gränssnittsrutin med hjälp av de konverterade värdena, och

  3. Konvertera datavärdena från anropet från Python till C.

Som du kan se byts datakonverteringsstegen helt enkelt ut för att passa den olika riktningen för överföringen mellan språken. Den enda skillnaden är den rutin som du anropar mellan de båda datakonverteringarna. Vid utökning anropar du en C-rutin, vid inbäddning anropar du en Python-rutin.

Detta kapitel kommer inte att diskutera hur man konverterar data från Python till C och vice versa. Även korrekt användning av referenser och hantering av fel förutsätts vara förstått. Eftersom dessa aspekter inte skiljer sig från att utöka tolken, kan du hänvisa till tidigare kapitel för den information som krävs.

1.3. Ren inbäddning

Det första programmet syftar till att exekvera en funktion i ett Python-skript. Precis som i avsnittet om gränssnittet på mycket hög nivå interagerar Python-tolken inte direkt med programmet (men det kommer att ändras i nästa avsnitt).

Koden för att köra en funktion som definieras i ett Python-skript är:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pValue;
    int i;

    if (argc < 3) {
        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
        return 1;
    }

    Py_Initialize();
    pName = PyUnicode_DecodeFSDefault(argv[1]);
    /* Error checking of pName left out */

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        /* pFunc is a new reference */

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(argc - 3);
            for (i = 0; i < argc - 3; ++i) {
                pValue = PyLong_FromLong(atoi(argv[i + 3]));
                if (!pValue) {
                    Py_DECREF(pArgs);
                    Py_DECREF(pModule);
                    fprintf(stderr, "Cannot convert argument\n");
                    return 1;
                }
                /* pValue reference stolen here: */
                PyTuple_SetItem(pArgs, i, pValue);
            }
            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);
            if (pValue != NULL) {
                printf("Result of call: %ld\n", PyLong_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"Call failed\n");
                return 1;
            }
        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
        return 1;
    }
    if (Py_FinalizeEx() < 0) {
        return 120;
    }
    return 0;
}

Den här koden laddar ett Python-skript med hjälp av argv[1] och anropar funktionen som namnges i argv[2]. Dess heltalsargument är de andra värdena i argv-arrayen. Om du kompilerar och länkar detta program (låt oss kalla den färdiga körbara filen call), och använder den för att köra ett Python-skript, t.ex:

def multiplicera(a,b):
    print("Beräknar", a, "gånger", b)
    c = 0
    för i i intervallet(0, a):
        c = c + b
    returnerar c

då borde resultatet bli:

$ samtal multiplicera multiplicera 3 2
Beräknar 3 gånger 2
Resultatet av anropet: 6

Även om programmet är ganska stort för sin funktionalitet, är det mesta av koden för datakonvertering mellan Python och C, och för felrapportering. Den intressanta delen när det gäller inbäddning av Python börjar med

Py_Initialize();
pName = PyUnicode_DecodeFSDefault(argv[1]);
/* Error checking of pName left out */
pModule = PyImport_Import(pName);

Efter initiering av tolken laddas skriptet med hjälp av PyImport_Import(). Denna rutin behöver en Python-sträng som sitt argument, vilken konstrueras med hjälp av datakonverteringsrutinen PyUnicode_DecodeFSDefault().

pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc är en ny referens */

if (pFunc && PyCallable_Check(pFunc)) {
    ...
}
Py_XDECREF(pFunc);

När skriptet har laddats hämtas det namn vi letar efter med hjälp av PyObject_GetAttrString(). Om namnet finns och objektet som returneras är anropsbart kan man lugnt anta att det är en funktion. Programmet fortsätter sedan med att konstruera en tupel av argument som normalt. Anropet till Python-funktionen görs sedan med:

pValue = PyObject_CallObject(pFunc, pArgs);

När funktionen returneras är pValue antingen NULL eller så innehåller den en referens till funktionens returvärde. Var noga med att släppa referensen efter att ha undersökt värdet.

1.4. Utökning av inbäddad Python

Fram till nu har den inbäddade Python-tolken inte haft tillgång till funktionalitet från själva applikationen. Python API tillåter detta genom att utöka den inbäddade tolken. Det vill säga, den inbäddade tolken utökas med rutiner som tillhandahålls av applikationen. Även om det låter komplicerat är det inte så illa. Glöm bara för ett tag att applikationen startar Python-tolken. Betrakta istället applikationen som en uppsättning underrutiner och skriv lite limkod som ger Python tillgång till dessa rutiner, precis som du skulle skriva ett vanligt Python-tillägg. Till exempel:

static int numargs=0;

/* Return the number of arguments of the application command line */
static PyObject*
emb_numargs(PyObject *self, PyObject *args)
{
    if(!PyArg_ParseTuple(args, ":numargs"))
        return NULL;
    return PyLong_FromLong(numargs);
}

static PyMethodDef emb_module_methods[] = {
    {"numargs", emb_numargs, METH_VARARGS,
     "Return the number of arguments received by the process."},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef emb_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "emb",
    .m_size = 0,
    .m_methods = emb_module_methods,
};

static PyObject*
PyInit_emb(void)
{
    return PyModuleDef_Init(&emb_module);
}

Infoga ovanstående kod precis ovanför funktionen main(). Infoga också följande två satser före anropet till Py_Initialize():

numargs = argc;
PyImport_AppendInittab("emb", &PyInit_emb);

Dessa två rader initierar variabeln numargs och gör funktionen emb.numargs`() tillgänglig för den inbäddade Python-tolken. Med dessa tillägg kan Python-skriptet göra saker som

import emb
print("Number of arguments", emb.numargs())

I en verklig applikation kommer metoderna att exponera ett API för applikationen till Python.

1.5. Inbäddning av Python i C++

Det är också möjligt att bädda in Python i ett C++-program; exakt hur detta görs beror på detaljerna i det C++-system som används; i allmänhet måste du skriva huvudprogrammet i C++ och använda C++-kompilatorn för att kompilera och länka ditt program. Det finns inget behov av att kompilera om Python själv med hjälp av C++.

1.6. Kompilering och länkning under Unix-liknande system

Det är inte nödvändigtvis trivialt att hitta rätt flaggor att skicka till din kompilator (och länkare) för att bädda in Python-tolken i din applikation, särskilt eftersom Python måste ladda biblioteksmoduler som implementeras som dynamiska C-tillägg (.so-filer) länkade mot den.

För att ta reda på vilka kompilator- och länkarflaggor som krävs kan du köra skriptet pythonX.Y-config som genereras som en del av installationsprocessen (ett skript python3-config kan också finnas tillgängligt). Detta skript har flera alternativ, av vilka följande kommer att vara direkt användbara för dig:

  • pythonX.Y-config --cflags kommer att ge dig de rekommenderade flaggorna vid kompilering:

    $ /opt/bin/python3.11-config --cflags
    -I/opt/include/python3.11 -I/opt/include/python3.11 -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall
    
  • pythonX.Y-config --ldflags --embed kommer att ge dig de rekommenderade flaggorna när du länkar:

    $ /opt/bin/python3.11-config --ldflags --embed
    -L/opt/lib/python3.11/config-3.11-x86_64-linux-gnu -L/opt/lib -lpython3.11 -lpthread -ldl -lutil -lm
    

Anteckning

För att undvika förväxling mellan olika Python-installationer (och särskilt mellan systemets Python och ditt eget kompilerade Python) rekommenderas att du använder den absoluta sökvägen till pythonX.Y-config, som i exemplet ovan.

Om den här proceduren inte fungerar för dig (det är inte garanterat att den fungerar för alla Unix-liknande plattformar; vi välkomnar dock bug reports) måste du läsa ditt systems dokumentation om dynamisk länkning och/eller undersöka Pythons Makefile (använd sysconfig.get_makefile_filename() för att hitta dess plats) och kompileringsalternativ. I det här fallet är modulen sysconfig ett användbart verktyg för att programmatiskt extrahera de konfigurationsvärden som du vill kombinera tillsammans. Till exempel

>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl  -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'