Frågor och svar om utbyggnad och inbäddning

Kan jag skapa mina egna funktioner i C?

Ja, du kan skapa inbyggda moduler som innehåller funktioner, variabler, undantag och till och med nya typer i C. Detta förklaras i dokumentet Utöka och bädda in Pythons tolkprogram.

De flesta Python-böcker på mellan- eller avancerad nivå tar också upp detta ämne.

Kan jag skapa mina egna funktioner i C++?

Ja, med hjälp av C-kompatibilitetsfunktionerna som finns i C++. Placera extern "C" { ... } runt Python-inkluderingsfilerna och sätt extern "C" före varje funktion som kommer att anropas av Python-tolken. Globala eller statiska C++-objekt med konstruktörer är förmodligen inte en bra idé.

Att skriva C är svårt; finns det några alternativ?

Det finns ett antal alternativ till att skriva dina egna C-tillägg, beroende på vad du försöker göra. Recommended third party tools erbjuder både enklare och mer sofistikerade metoder för att skapa C- och C++-tillägg för Python.

Hur kan jag köra godtyckliga Python-satser från C?

Funktionen på högsta nivå för att göra detta är PyRun_SimpleString() som tar ett enda strängargument som ska köras i kontexten för modulen __main__ och returnerar 0 för framgång och -1 när ett undantag inträffade (inklusive SyntaxError`). Om du vill ha mer kontroll kan du använda PyRun_String(); se källan för PyRun_SimpleString() i Python/pythonrun.c.

Hur kan jag utvärdera ett godtyckligt Python-uttryck från C?

Anropa funktionen PyRun_String() från föregående fråga med startsymbolen Py_eval_input; den tolkar ett uttryck, utvärderar det och returnerar dess värde.

Hur extraherar jag C-värden från ett Python-objekt?

Det beror på objektets typ. Om det är en tupel returnerar PyTuple_Size() dess längd och PyTuple_GetItem() returnerar objektet vid ett angivet index. Listor har liknande funktioner, PyList_Size() och PyList_GetItem().

För byte returnerar PyBytes_Size() dess längd och PyBytes_AsStringAndSize() ger en pekare till dess värde och dess längd. Observera att Python bytes-objekt kan innehålla null bytes så C:s strlen() bör inte användas.

För att testa typen av ett objekt, se först till att det inte är NULL, och använd sedan PyBytes_Check(), PyTuple_Check(), PyList_Check(), etc.

Det finns också ett högnivå-API för Python-objekt som tillhandahålls av det så kallade ”abstrakta” gränssnittet - läs Include/abstract.h för ytterligare detaljer. Det möjliggör gränssnitt mot alla typer av Python-sekvenser med hjälp av anrop som PySequence_Length(), PySequence_GetItem(), etc. samt många andra användbara protokoll som nummer (PyNumber_Index() et al.) och mappningar i PyMapping API.

Hur använder jag Py_BuildValue() för att skapa en tupel av godtycklig längd?

Det kan du inte göra. Använd PyTuple_Pack() istället.

Hur anropar jag ett objekts metod från C?

Funktionen PyObject_CallMethod() kan användas för att anropa en godtycklig metod för ett objekt. Parametrarna är objektet, namnet på den metod som skall anropas, en formatsträng som den som används med Py_BuildValue(), och argumentvärdena:

PyObject * PyObject
PyObject_CallMethod(PyObject *object, const char *method_name,
                    const char *arg_format, ...);

Detta fungerar för alla objekt som har metoder - oavsett om de är inbyggda eller användardefinierade. Du är ansvarig för att så småningom Py_DECREF()’a returvärdet.

Så här anropar du t.ex. ett filobjekts metod ”seek” med argumenten 10, 0 (förutsatt att filobjektets pekare är ”f”):

res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0);
if (res == NULL) {
        ... ett undantag inträffade ...
}
else {
        Py_DECREF(res);
}

Observera att eftersom PyObject_CallObject() alltid vill ha en tupel för argumentlistan, ska du för att anropa en funktion utan argument ange ”()” för formatet, och för att anropa en funktion med ett argument ska du omge argumentet med parenteser, t.ex. ”(i)”.

Hur fångar jag utdata från PyErr_Print() (eller något som skrivs ut till stdout/stderr)?

I Python-kod definierar du ett objekt som stöder metoden write(). Tilldela detta objekt till sys.stdout och sys.stderr. Anropa print_error, eller låt bara standardmekanismen för spårning fungera. Sedan kommer utdata att gå dit din write()-metod skickar den.

Det enklaste sättet att göra detta är att använda io.StringIO-klassen:

>>> import io, sys
>>> sys.stdout = io.StringIO()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(sys.stdout.getvalue())
foo
hello world!

Ett anpassat objekt för att göra samma sak skulle se ut så här:

>>> import io, sys
>>> class StdoutCatcher(io.TextIOBase):
...     def __init__(self):
...         self.data = []
...     def write(self, stuff):
...         self.data.append(stuff)
...
>>> import sys
>>> sys.stdout = StdoutCatcher()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(''.join(sys.stdout.data))
foo
hello world!

Hur kommer jag åt en modul som är skriven i Python från C?

Du kan få en pekare till modulobjektet på följande sätt:

module = PyImport_ImportModule("<modulename>");

Om modulen inte har importerats ännu (dvs. den finns ännu inte i sys.modules), initieras modulen; annars returneras helt enkelt värdet av sys.modules["<modulename>"]. Observera att modulen inte skrivs in i något namnrymd – den säkerställer bara att den har initialiserats och lagras i sys.modules.

Du kan sedan komma åt modulens attribut (dvs. alla namn som definieras i modulen) på följande sätt:

attr = PyObject_GetAttrString(module, "<attrname>");

Att anropa PyObject_SetAttrString() för att tilldela variabler i modulen fungerar också.

Hur skapar jag gränssnitt till C++-objekt från Python?

Beroende på dina krav finns det många tillvägagångssätt. För att göra detta manuellt, börja med att läsa dokumentet ”Extending and Embedding”. Inse att för Pythons körtidssystem är det inte så stor skillnad mellan C och C++ - så strategin att bygga en ny Python-typ runt en C-strukturtyp (pekare) fungerar också för C++-objekt.

För C++-bibliotek, se Att skriva C är svårt; finns det några alternativ?.

Jag lade till en modul med hjälp av installationsfilen och make misslyckas; varför?

Setup måste sluta med en ny rad, om det inte finns någon ny rad där misslyckas byggprocessen. (För att åtgärda detta krävs en del fula skalskript, och den här buggen är så liten att det inte verkar värt besväret)

Hur felsöker jag ett tillägg?

När du använder GDB med dynamiskt laddade tillägg kan du inte ställa in en brytpunkt i ditt tillägg förrän ditt tillägg är laddat.

I filen .gdbinit (eller interaktivt) lägger du till kommandot:

br _PyImport_LoadDynamicModule

När du sedan kör GDB:

$ gdb /local/bin/python
gdb) run myscript.py
gdb) continue # repeat until your extension is loaded
gdb) finish   # so that your extension is loaded
gdb) br myfunction.c:50
gdb) continue

Jag vill kompilera en Python-modul på mitt Linux-system, men vissa filer saknas. Hur kommer det sig?

De flesta paketerade versioner av Python utelämnar vissa filer som krävs för att kompilera Python-tillägg.

För Red Hat installerar du python3-devel RPM för att få de nödvändiga filerna.

För Debian, kör apt-get install python3-dev.

Hur skiljer jag ”ofullständiga uppgifter” från ”ogiltiga uppgifter”?

Ibland vill man efterlikna Pythons interaktiva tolk, där man får en fortsättningsprompt när inmatningen är ofullständig (t.ex. om man har skrivit början på en ”if”-sats eller inte stängt parenteserna eller de tredubbla strängcitaten), men ett syntaxfelmeddelande direkt när inmatningen är ogiltig.

I Python kan du använda modulen codeop, som approximerar parserns beteende på ett tillfredsställande sätt. IDLE använder till exempel detta.

Det enklaste sättet att göra det i C är att anropa PyRun_InteractiveLoop() (kanske i en separat tråd) och låta Python-tolken hantera inmatningen åt dig. Du kan också ställa in PyOS_ReadlineFunctionPointer() för att peka på din anpassade inmatningsfunktion. Se Modules/readline.c och Parser/myreadline.c för fler tips.

Hur hittar jag odefinierade G++-symboler __builtin_new eller __pure_virtual?

För att dynamiskt ladda g++-tilläggsmoduler måste du kompilera om Python, länka om det med g++ (ändra LINKCC i Python Modules Makefile) och länka din tilläggsmodul med g++ (t.ex. g++ -shared -o mymodule.so mymodule.o).

Kan jag skapa en objektklass där vissa metoder är implementerade i C och andra i Python (t.ex. genom arv)?

Ja, du kan ärva från inbyggda klasser som int, list, dict, etc.

Boost Python Library (BPL, https://www.boost.org/libs/python/doc/index.html) erbjuder ett sätt att göra detta från C++ (dvs. du kan ärva från en tilläggsklass skriven i C++ med hjälp av BPL).