2012-04-21 9 views
7

Sto solo iniziando a giocare con le estensioni Python C e sono curioso di sapere perché una funzione C, che può essere richiamata da Python, deve prendere 2 argomenti PyObject * e restituire un PyObject *. Ho scritto il seguente "Ciao Mondo" estensione:Estensioni Python C - Perché le funzioni C callable accettano argomenti e restituiscono PyObject *

#include <Python.h> 

static PyObject * 
hello_world(PyObject *self, PyObject *noargs) 
{ 
    printf("Hello World\n"); 
    return Py_BuildValue(""); 
} 


// Module functions table. 

static PyMethodDef 
module_functions[] = { 
    { "hello_world", hello_world, METH_NOARGS, "hello world method" }, 
    { NULL } 
}; 


// This function is called to initialize the module. 
PyMODINIT_FUNC 
inittesty2(void) 
{ 
    Py_InitModule("testy2", module_functions); 
} 

Perché non posso (in particolare con METH_NOARGS) utilizzare il seguente metodo hello_world:

static void 
hello_world() 
{ 
    printf("Hello World\n"); 
} 

?

risposta

10

Ci sono molte cose da dire sui vari puntatori PyObject.

  1. quella richiesta come tipo ritorno viene utilizzato per il meccanismo di gestione eccezioni. In particolare, se la funzione restituisce un puntatore nullo, l'interprete Python genererà un'eccezione. (Si dovrebbe fare solo dopo aver chiamato uno dei PyErr_.. funzioni per impostare una deroga specifica.)

    Questo significa anche che ogni volta che non si vuole generare un'eccezione, si necessario restituire un puntatore a qualche reale PyObject. Se non c'è nulla in particolare che la funzione dovrebbe restituire, è sufficiente restituire Py_None (utilizzare la macro Py_RETURN_NONE per ottenere correttamente il conteggio dei riferimenti) o "true" (utilizzando Py_RETURN_TRUE).

  2. Il primo argomento, PyObject *self punta all'oggetto la funzione viene chiamata da, o per l'istanza di modulo a cui appartiene. Notare che ogni funzione definita è un metodo di classe o un metodo di modulo. Non ci sono funzioni totalmente indipendenti.

  3. Il seconda argomento, PyObject *args indica l'argomento della funzione (che può essere una tupla o una lista di più argomenti). Hai ragione nel sottolineare che una funzione che non accetta argomenti non dovrebbe aver bisogno di questo — e, per quanto posso dire, hai ragione.Non devi definirlo; si può semplicemente definire una funzione come

    static PyObject *PyMyClass_MyFunc(PyObject *self) { 
        /* ..do something.. */ 
        Py_RETURN_TRUE; 
    } 
    

    Si dovrà ancora giocare questa a PyCFunction quando lo metti in PyMethodDef per il tipo di dati si definisce, ma credo che cast è al sicuro fino a quando si utilizzare il flag METH_NOARGS. Notate tuttavia i commenti seguenti per possibili rischi.

  4. Infine, una funzione può in effetti avere un terzo argomento come questo:

    static PyObject *PyMyClass_Func(PyObject *self, PyObject *args, PyObject *kwds) 
    { 
        /*...*/ 
    } 
    

    Il terzo argomento è usato per nome, opzionali argomenti. Anche in questo caso, è necessario il eseguire il puntatore alla funzione PyCFunction, ma anche questo è sicuro se si imposta il flag corretto (METH_KEYWORDS) per la funzione nella tabella dei metodi.

+0

È 'Py_None', non' PyNone'. Inoltre c'è 'Py_RETURN_NONE' simile a' Py_RETURN_TRUE' e 'Py_RETURN_FALSE'. – yak

+0

Per (3), correggimi se ho torto ma se il codice utilizzava una convenzione di chiamata in cui tutti gli argomenti vengono passati nello stack e il chiamato (la funzione) li fa fuori dallo stack, la rimozione dell'argomento non utilizzato causerebbe un errore arresto anomalo perché Python passerà sempre NULL come secondo argomento e la funzione non lo popolerà. – yak

+0

@ yak Non provoca un arresto anomalo nel mio codice. – jogojapan

2

Poiché le funzioni di Python, anche quelle banali che si limitano a stampare su stdout, sono più che semplici wrapper attorno alle funzioni C.

Nel caso più semplice, pensate alle strutture di introspezione in Python. Una funzione di Python è un oggetto pieno fledge che è possibile interrogare:

>>> def hello(): 
...  print 'hello' 
... 
>>> dir(hello) 
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name'] 

Si potrebbe naturalmente immaginare un impianto di un'estensione che ha appena concluso le funzioni C. Controlla SWIG, che consente alle estensioni di scrittura di molti linguaggi di scripting, incluso Python. Perché sta andando per il minimo comune denominatore, ti permetterà di avvolgere una funzione come la tua hello_world, ma ovviamente perdi anche molto potere.

4

Il primo argomento per le funzioni a livello di modulo è l'oggetto modulo. Quando si definiscono le classi in C (la stessa struttura PyMethodDef viene utilizzata per i metodi lì), il primo argomento è l'istanza (simile a self in Python).

Quando si utilizza METH_NOARGS, Python passerà a NULL come secondo argomento. Potevano lanciarlo in una funzione con un argomento, ma immagino che non pensassero che fosse necessario.

Il valore restituito è facile da spiegare. Ogni funzione Python ha un valore di ritorno. Se non si utilizza esplicitamente lo return in Python, la funzione restituirà None.

Ovviamente in C devi essere esplicito sul valore di ritorno, quindi se non lo usi, devi restituire None da solo. Python fornisce una macro per questo:

Py_RETURN_NONE; 

In alternativa è possibile accedere al None istanza globale da soli:

Py_INCREF(Py_None); 
return Py_None; 

ma la macro è più facile da usare.

Si potrebbe pensare che restituire NULL dovrebbe essere equivalente a None ma NULL viene utilizzato per indicare che la funzione ha generato un'eccezione.

Problemi correlati