Title: FAQ Estendere/Includere Python Content-type: text/x-rst

1   FAQ Estendere/Includere Python

Date:$Date: 2004-04-08 17:05:47 +0200 (gio, 08 apr 2004) $
Version:$Revision: 7294 $
Web site:http://www.python.org/
Traduzione:Bartolomeo Bogliolo 1 Aprile 2006

Contents

Traduzione Italiana

Questo documento, di pubblico dominio, e' stato tradotto da Meo Bogliolo (revisionato da Daniele Tricoli ): potete contattarlo per qualsiasi commento o correzione.

1.1   Si possono creare proprie funzioni in C?

Si, si possono creare moduli built-in che contengono funzioni, variabili, eccezioni ed anche nuovi tipi in C. Tutto cio' e' spiegato nel documento "Extending and Embedding the Python Interpreter" (http://docs.python.org/ext/ext.html).

Anche la maggior parte dei libri di livello intermedio o avanzato su Python trattano l'argomento.

1.2   Si possono creare proprie funzioni in C++?

Si, utilizzando le caratteristiche di compatibilita' al C tipiche del C++. Si deve porre extern "C" { ... } a delimitare gli include file di Python e porre extern "C" prima di ogni funzione che debba essere chiamata dall'inteprete Python. Oggetti C++ globali o statici con costruttori non sono adatti per tale utilizzo.

1.3   Scrivere in C e' difficile; ci sono altre possibilita'?

Vi sono una serie di alternative a scrivere estensioni in C, dipende da cosa si cerca di fare.

Se e' necessaria una maggior velocita' di esecuzione Psyco genera codice assembler x86 dal bytecode Python. E' possibile utilizzare Psyco per compilare le funzioni piu' critiche del codice ed ottenere un notevole incremento di prestazioni con uno sforzo molto limitato, naturalmente se si utilizza un sistema con un processore compatibile a x86.

Pyrex e' un compilatore che accetta una sintassi leggermente modifica di Pytjon e genera il corrispondente codice C. Pyrex consente di scrivere un'estensione senza dover imparare le API C di Python.

Se debbono essere interfacciate librerie C per con non esista ancora un'estensione per Python e' si puo' provare ad effettuare un wrapping delle funzioni e dei tipi di dati con SWIG. Per le librerie C++ si possono utilizzare SIP, CXX, Boost o Weave.

1.4   Come si possono eseguire comandi Python dal C?

La funzione piu' ad alto livello e' PyRun_SimpleString() che riceve come argomento una singola stringa che deve essere eseguita nel contesto del modulo __main__ e restituisce 0 in caso di successo e -1 quando occorre un'eccezione (compreso SyntaxError). Se e' necessario un maggior controllo deve essere utilizzato PyRun_String(); si veda il sorgente di PyRun_SimpleString() in Python/pythonrun.c.

1.5   Si puo' calcolare il valore di un'espressione Python dal C?

Richiamando la funzione PyRun_String(), citata nella domanda precedente, con il simbolo di partenza Py_eval_input; essa parsifica un espressione, la valuta e restituisce il suo valore.

1.6   Come si estraggono i valori in C da un oggetto Python?

Dipende dal tipo d'oggetto. Se e' una tupla PyTupleSize(o) restituisce la sua lunghezza e PyTuple_GetItem(o, i) restituisce il suo i-esimo elemento. Le liste hanno funzioni simili: PyListSize(o) e PyList_GetItem(o, i).

Per le stringhe PyString_Size(o) restituisce la sua lunghezza e PyString_AsString(o) un puntatore al suo valore. Si noti che le stringhe in Python possono contenere byte a null e quindi la funzione C strlen() non deve essere utilizzata.

Per controllare il tipo di un oggetto innanzi tutto bisogna assicurarsi che non sia NULL e quindi utilizzare PyString_Check(o), PyTuple_Check(o), PyList_Check(o), ecc.

E' anche disponibile un'API di alto livello agli oggetti Python che e' fornita dall'interfaccia chiamata 'astratta' -- si legga Include/abstract.h per maggiori dettagli. Permette di interfacciarsi con ogni titpo di sequenza Python utilizzando chiamate come PySequence_Length(), PySequence_GetItem(), ecc.) cosi' come altri utili protocolli.

1.7   Come si puo' richiamare Py_BuildValue() per creare una tupla di lunghezza arbitraria?

Non e' possibile. E' necessario utilizzare Use t = PyTuple_New(n) e caricarla con gli oggetti utilizzando PyTuple_SetItem(t, i, o) -- si deve notare che questa tecnica "mangia" un contatore di riferimento ad o, quindi e' necessario richiamare Py_INCREF. Le liste hanno funzioni similari come PyList_New(n) e PyList_SetItem(l, i, o). Si noti che si deve impostare un valore a tutti gli elementi di una tupla prima di passare la tupla all'interprete Python -- PyTuple_New(n) inizializza i valori a NULL, che non e' un valore valido per Python.

1.8   Come si puo' richiamare un metodo di un oggetto in C?

La funzione PyObject_CallMethod() puo' essere utilizzata per richiamare un qualsiasi metodo di un oggetto. I parametri sono l'oggetto, il nome del metodo da richiamare, una stringa di formato come quella utilizzata con Py_BuildValue() ed il valore degli argomenti:

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

Questo funziona per ogni oggetto che abbia metodi -- sia che siano built-in che definiti dall'utente. Il programmatore ha la responsabilita' di lanciare Py_DECREF sul valore di ritorno.

Ad esempio per richiamare il metodo "seek" su un oggetto file con argomenti 10, 0 ("f" e' il puntatore all'oggetto file):

res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0);
if (res == NULL) {
        ... an exception occurred ...
}
else {
        Py_DECREF(res);
}

Poiche' PyObject_CallObject() richiede sempre una tupla come lista di argomenti, per richiamare una funzione senza argomenti deve essere passato "()" come formato e per chiamare una funzione con un argomento questo va posto tra parentesi: "(i)".

1.9   Come si puo' raccogliere l'output da PyErr_Print() (o da qualunque cosa che scriva su standard output/error)?

Nel codice Python si deve definire un oggetto che supporti il metodo write(). Quindi va assegnato questo oggetto a sys.stdout e sys.stderr. Ora chiamando print_error o lasciando operare il meccanismo di traceback standard l'output sara' ridiretto ovunque il metodo write() lo invia.

Il modo piu' semplice di farlo e' utilizzare la classe StringIO della libreria standard.

Codice d'esempio per catturare stdout:

>>> class StdoutCatcher:
...     def __init__(self):
...         self.data = ''
...     def write(self, stuff):
...         self.data = self.data + stuff
...
>>> import sys
>>> sys.stdout = StdoutCatcher()
>>> print 'foo'
>>> print 'hello world!'
>>> sys.stderr.write(sys.stdout.data)
foo
hello world!

1.10   Come si accede ad un modulo Python dal C?

Per ottenere un puntatore al modulo dell'oggetto si utilizza:

module = PyImport_ImportModule("<modulename>");

Se il modulo non e' ancora stato importato (ovvero non e' ancora presente in sys.modules), questo viene inizializzato; altrimenti restituisce semplicemente il valore di sys.modules["<modulename>"]. Deve essere notato che questo non inserisce il modulo in alcun namespace -- assicura solamente che il modulo sia inizializzato e caricato in sys.modules.

A questo punto e' possibile accedere agli attributi del modulo (ogni nome definito nel modulo) come segue:

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

Chiamando PyObject_SetAttrString() si possono anche assegnare valori alle variabili del modulo.

1.11   Come si interfaccia un oggetto C++ da Python?

A seconda delle necessita' vi sono differenti soluzioni. Per farlo manualmente si consiglia di iniziare a leggere il documento "Extending and Embedding". Per quello che riguard il run-time di Python non c'e' una grande differenza tra il C ed il C++ -- percio' la strategia di costruire un nuovo tipo Python intorno ad un tipo struttura C (puntatore) funziona anche con i gli oggetti C++.

Per le librerie C++ si possono utilizzare SIP, CXX, Boost, o Weave. SWIG e' un tool automatico simile che supporta solo librerie C.

1.12   Aggiungendo un modulo nel file di Setup il make fallisce; perche?

Il Setup deve finire con un carattere di fine riga, se non e' presente la costruizione fallisce. (Risolvere questo problema richiederebbe un noioso shell script, ed il bug e' cosi' poco importante che non ne vale la pena.)

1.13   Come si effettua il debug di un'estensione?

Utilizzando ul GDB con estensioni caricate dinamicamente, non e' possibile impostare un breakpoint nell'estensione fino a che non e' caricata.

Basta aggiungere nel file .gdbinit (o eseguire interattivamente), il comando:

br _PyImport_LoadDynamicModule

Quindi, eseguendo 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

1.14   Cercando di compilare un modulo Python su un sistema Linux risultano mancanti alcuni file. Perche?

La maggior parte delle versioni a pacchetto di Python non includono la directory /usr/lib/python2.x/config/ che contiene alcuni file necessari per compilare le estensioni Python.

Per Red Hat e' necessario installare l'RPM python-devel.

Per Debian, e' necessario lanciare il comando apt-get install python-dev.

1.15   Cosa significa "SystemError: _PyImport_FixupExtension: module nomemodulo not loaded"?

Vuol dire che e' stata creato un modulo d'estensione chiamato "nomemodulo", ma la funzione di init non si inizializza con quel nome.

Ogni funzione di init di un modulo deve avere una linea simile a:

module = Py_InitModule("nomemodulo", nomemodulo_functions);

Se la stringa passata a questa funzione non ha lo stresso nome del modulo d'estensione viene sollevata l'eccezione SystemError.

1.16   Come e' possibile restituire "incomplete input" a fronte di un "invalid input"?

A volte si vuole emulare il comportamento dell'interprete interattivo di Python che restituisce un prompt di continuazione quando l'input non e' completo (e.g. e' stato digitato l'inizio di una istruzione di "if", non e' stata chiusa una parentesi o gli apici tripli di stringa) ma restituisce un errore di sintassi immediatamente quando l'input non e' corretto.

In Python si puo' utilizzare il modulo codeop, che approssima il comportamento del parser a sufficenza. Ad esempio lo utilizza IDLE.

Il modo piu' facile per farlo in C e' richiamare PyRun_InteractiveLoop() (magari in un thread separato) e lasciare che sia l'inteprete Python a gestire l'input. E' anche possibile far puntare PyOS_ReadlineFunctionPointer ad una routine custom di gestione dell'input. Si vedano Modules/readline.c e Parser/myreadline.c per ulteriori indicazioni.

Tuttavia a volte si deve eseguire l'inteprete Python nello stesso thread dell'applicazione e non e' possibile far interropere the same thread as your rest application and you can't allow the PyRun_InteractiveLoop() in attesa di un input dell'utente. L'unica soluzione e' chiamare PyParser_ParseString() e controllare quando e.error e' uguale a E_EOF (che indica che l'input non e' completo). Ecco un frammento di codice d'esempio, non testato, ispirato ad un programma di Alex Farber:

#include <Python.h>
#include <node.h>
#include <errcode.h>
#include <grammar.h>
#include <parsetok.h>
#include <compile.h>

int testcomplete(char *code)
  /* code should end in \n */
  /* return -1 for error, 0 for incomplete, 1 for complete */
{
  node *n;
  perrdetail e;

  n = PyParser_ParseString(code, &_PyParser_Grammar,
                           Py_file_input, &e);
  if (n == NULL) {
    if (e.error == E_EOF)
      return 0;
    return -1;
  }

  PyNode_Free(n);
  return 1;
}

Un'altra soluzione e' quella di compilare la stringa ricevuta con Py_CompileString(). Se si compila senza errori si puo' provare ad eseguire il codice chimando PyEval_EvalCode(). Altrimenti si salva l'input per dopo. Se la compilazione fallisce si deve cercare se si tratta di un errore o se e' richiesto altro input estraendo il messaggio dalla tupla dell'eccezione e confrontandola con la stringa "unexpected EOF while parsing". Ecco un esempio completo che utilizza la libreria GNU readline (si puo' ignorare SIGINT nel richiamare readline()):

#include <stdio.h>
#include <readline.h>

#include <Python.h>
#include <object.h>
#include <compile.h>
#include <eval.h>

int main (int argc, char* argv[])
{
  int i, j, done = 0;                          /* lengths of line, code */
  char ps1[] = ">>> ";
  char ps2[] = "... ";
  char *prompt = ps1;
  char *msg, *line, *code = NULL;
  PyObject *src, *glb, *loc;
  PyObject *exc, *val, *trb, *obj, *dum;

  Py_Initialize ();
  loc = PyDict_New ();
  glb = PyDict_New ();
  PyDict_SetItemString (glb, "__builtins__", PyEval_GetBuiltins ());

  while (!done)
  {
    line = readline (prompt);

    if (NULL == line)                          /* CTRL-D pressed */
    {
      done = 1;
    }
    else
    {
      i = strlen (line);

      if (i > 0)
        add_history (line);                    /* save non-empty lines */

      if (NULL == code)                        /* nothing in code yet */
        j = 0;
      else
        j = strlen (code);

      code = realloc (code, i + j + 2);
      if (NULL == code)                        /* out of memory */
        exit (1);

      if (0 == j)                              /* code was empty, so */
        code[0] = '\0';                        /* keep strncat happy */

      strncat (code, line, i);                 /* append line to code */
      code[i + j] = '\n';                      /* append '\n' to code */
      code[i + j + 1] = '\0';

      src = Py_CompileString (code, "<stdin>", Py_single_input);

      if (NULL != src)                         /* compiled just fine - */
      {
        if (ps1  == prompt ||                  /* ">>> " or */
            '\n' == code[i + j - 1])           /* "... " and double '\n' */
        {                                               /* so execute it */
          dum = PyEval_EvalCode ((PyCodeObject *)src, glb, loc);
          Py_XDECREF (dum);
          Py_XDECREF (src);
          free (code);
          code = NULL;
          if (PyErr_Occurred ())
            PyErr_Print ();
          prompt = ps1;
        }
      }                                        /* syntax error or E_EOF? */
      else if (PyErr_ExceptionMatches (PyExc_SyntaxError))
      {
        PyErr_Fetch (&exc, &val, &trb);        /* clears exception! */

        if (PyArg_ParseTuple (val, "sO", &msg, &obj) &&
            !strcmp (msg, "unexpected EOF while parsing")) /* E_EOF */
        {
          Py_XDECREF (exc);
          Py_XDECREF (val);
          Py_XDECREF (trb);
          prompt = ps2;
        }
        else                                   /* some other syntax error */
        {
          PyErr_Restore (exc, val, trb);
          PyErr_Print ();
          free (code);
          code = NULL;
          prompt = ps1;
        }
      }
      else                                     /* some non-syntax error */
      {
        PyErr_Print ();
        free (code);
        code = NULL;
        prompt = ps1;
      }

      free (line);
    }
  }

  Py_XDECREF(glb);
  Py_XDECREF(loc);
  Py_Finalize();
  exit(0);
}

1.17   Come si cercano simboli g++ non definiti __builtin_new o __pure_virtual?

Per caricare i moduli di estensione g++ bisogna ricompilare Python, relinkarlo utilizzando g++ (cambiando LINKCC nel Makefile dei Moludi Python) e linkare i moduli d'estensione utilizzando il g++ (e.g., "g++ -shared -o mymodule.so mymodule.o").

1.18   Si possono creare classi di oggetti con metodi implementati in C ed altri in Python (eg. mediante ereditarieta')?

A partire da Python 2.2, si puo' ereditare dalle classi builtin come int, list, dict, ecc.

La Boost Python Library (BPL, http://www.boost.org/libs/python/doc/index.html) fornisce un modo per farlo dal C++ (ad esempio e' possibile ereditare da una classe d'estensione scritta in C++ utilizzando il BPL).

1.19   Perche' importando un modulo si verifica l'errore "undefined symbol: PyUnicodeUCS2*"?

Poiche' si sta utilizzando una versione di Python che utilizza una rappresentazione a 4 byte dei caratteri Unicode mentre si sta importando un modulo che e' stato compilato con un Python che utilizza una rappresentazione a 2 byte (che e' il default).

Se il nome del simbolo non definito che inizia con PyUnicodeUCS4, il problema e' l'opposto: Python utilizza caratteri Unicode a 2 byte ed il modulo d'estensione e' stato compilato con un Python che utilizza caratteri Unicode a 4 byte.

Questo problema si verifica spesso utilizzando pacchetti d'estensione precostruiti. RedHat Linux 7.x, in particolare forniva un binario "python2" compilato con Unicode a 4 byte. Questo causa problemi di link solo se l'estensione utilizza una quasiasi funzione PyUnicode_*(). E' anche un problema se l'estensione utilizza una qualsiasi specifica di formato relativa agli Unicode per Py_BuildValue (o simili) oppure una specifica di parametri per PyArg_ParseTuple().

E' possibile verificare la dimensione di un carattere Unicode sull'interprete Python controllando il valore di sys.maxunicode:

>>> import sys
>>> if sys.maxunicode > 65535:
...     print 'UCS4 build'
... else:
...     print 'UCS2 build'

L'unico modo per risolvere il problema e' di utilizzare moduli d'estesione compilati con un binario Python che utilizzi la stessa dimensione per i caratteri Unicode.