Bookmark and Share
You are here: Home Documentazione Ricette Scrivere una semplice estensione C per Python
Document Actions

Scrivere una semplice estensione C per Python
medio

Breve guida che illustra nella pratica come scrivere una semplice estensione C richiamabile da Python.

Obiettivo

Scrivere una semplice estensione in C richiamabile da Python.

 

Prerequisiti

Conoscenza almeno basilare dei linguaggi C e Python.

 

Iniziamo!

In questa breve guida andremo a vedere come scrivere una semplice estensione in C richiamabile da Python.
Integrare C in Python può essere utile essenzialmente in due casi:

  • quando vi sono determinate porzioni di codice per cui è richiesta una maggiore velocità di esecuzione
  • quando ci si vuole interfacciare a chiamate del sistema operativo che non sono disponibili nativamente in Python

In questa breve guida andremo a prendere in esame il primo caso, ovvero appoggiarci ad un'estensione scritta in C per velocizzare il tempo di esecuzione di un determinato algoritmo.

 

Versioni a confronto: Python e C


Prendiamo come esempio una funzione che calcoli il fattoriale di un numero compreso tra 1 e 10.

In Python:

#!/usr/bin/env python
# factorial.py

def factorial(n):
    if (n < 1) or (n > 10):
        raise ValueError("Specifica un numero tra 1 e 10")
    fact = 1
    while n > 1:
        fact *= n
        n -= 1
    return fact

if __name__ == '__main__':
    print "Il fattoriale di 10 e': %s" %factorial(10)


In C:

//_factorial.c
#include <stdlib.h>

int factorial(int n)
{
    int fact = 1;
    if ((n < 1) || (n > 10)){
        printf("Specifica un numero tra 1 e 10");
        return -1;
    }
    while (n > 1){
        fact = fact * n;
        n = n - 1;
    }
    return fact;
}

int main()
{
    printf("Il fattoriale di 10 e': %d\n", factorial(10));
    return 1;
}
  • Nota: per compilare la versione C è sufficiente utilizzare il comando "gcc _factorial.c" che genererà un eseguibile chiamato "a.out"


Si può notare sin da subito la differenza di velocità delle due implementazioni calcolando il tempo di esecuzione tramite il comando time.
Utilizzando time possiamo notare come la versione C sia circa 8 volte più veloce di quella Python.
 

user@ubuntu:~/foo$ time python pyfactorial.py
Il fattoriale di 10 e': 3628800

real    0m0.049s
user   0m0.030s
sys     0m0.020s

user@ubuntu:~/foo$ time ./a.out
Il fattoriale di 10 e': 3628800

real    0m0.006s
user   0m0.000s
sys     0m0.010s
user@ubuntu:~/foo$

 

Integrare il codice C in Python


Abbiamo visto le due versioni dello stesso programma implementate nei due diversi linguaggi.
Quello che vogliamo fare ora è modificare la versione C affinchè sia richiamabile da Python come se fosse un comune modulo avente estensione .py.
Per fare ciò andremo ad eseguire una serie di mofiche sul file _cfactorial.c che, senza scendere troppo nel dettaglio (c'è l'apposita documentazione per questo), si possono riassumere in:

  •  dichiarare una routine dei nomi tramite PyMethodDef
  •  dichiarare una routine di inizializzazione
  •  modificare la funzione C scritta in precedenza affinchè accetti un oggetto Python di tipo int come argomento (tramite PyArg_ParseTuple) e ne ritorni un altro dello stesso tipo (tramite Py_BuildValue)


Il codice:

// _factorial.c
#include <Python.h>
#include <stdlib.h>

static PyObject* factorial(PyObject* self, PyObject* args);


// name routine
static PyMethodDef module_methods[] = {
    {"factorial",  factorial, METH_VARARGS, "Docstring"},
    {NULL, NULL, 0, NULL}
};


// initializer
PyMODINIT_FUNC
init_factorial(void)
{
    Py_InitModule3("_factorial", module_methods, "doc");
}


static PyObject*
factorial(PyObject* self, PyObject* args)
{
    int n;
    int fact = 1;

    /*
    args rappresenta la tupla di argomenti passata dal codice Python.
    Assumo che vi sia un solo argomento, mi aspetto che sia di tipo int,
    e lo salvo nella variabile "n" tramite "PyArg_ParseTuple".
    */
    if (! PyArg_ParseTuple(args, "i", &n)) {
        return PyErr_Format(PyExc_ValueError, "Invalid argument");
    }

    if ((n < 1) || (n > 10)){
        return PyErr_Format(PyExc_ValueError, "Specifica un numero tra 1 e 10");
    }

    while (n > 1){
        fact = fact * n;
        n = n - 1;
    }

    /*
    Py_BuildValue ritorna un tipo Python. In questo caso utilizzando
    l'argomento "i" specifico un tipo intero.
    */
    return Py_BuildValue("i", fact);
}

 

Terminate le modifiche sul codice C non ci resta che scrivere uno script setup.py che, utilizzando distutils, compili il codice C e lo trasformi in un modulo binario avente estensione .so (.pyd su Windows) importabile dall'interprete Python.

#!/usr/bin/env python
# setup.py

from distutils.core import setup, Extension

setup(name='factorial',
          ext_modules=[Extension('_factorial', ['_factorial.c'])],
          )


Ora che l'estensione è pronta è sufficiente installarla tramite il classico "python setup.py install":

user@ubuntu:~/foo$ sudo python setup.py install
running install
running build
running build_ext
building '_factorial' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/usr/local/include/python2.6 -c _factorial.c -o build/temp.linux-i686-2.6/_factorial.o
gcc -pthread -shared build/temp.linux-i686-2.6/_factorial.o -o build/lib.linux-i686-2.6/_factorial.so
running install_lib
copying build/lib.linux-i686-2.6/_factorial.so -> /usr/local/lib/python2.6/site-packages
running install_egg_info
Removing /usr/local/lib/python2.6/site-packages/cfactorial-0.0.0-py2.6.egg-info
Writing /usr/local/lib/python2.6/site-packages/cfactorial-0.0.0-py2.6.egg-info
user@ubuntu:~/foo$


Non rimane che lanciare l'interprete Python e utilizzare l'estensione C dall'interprete Python:

user@ubuntu:~/foo$ python
Python 2.6.1 (r261:67515, Mar  3 2009, 14:11:52)
[GCC 4.3.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import _factorial
>>> _factorial.factorial(10)
3628800
>>> _factorial.factorial(1)
1
>>> _factorial.factorial("string")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Invalid argument
>>>


Nota finale

Le operazioni descritte in questa guida possono essere velocizzate di molto utilizzando Swig.

 

Link utili

 

 

by Giampaolo Rodolà last modified 2009-06-05 15:42
 

Supporto

Ottieni un
aiuto veloce e mirato sul forum, gratis!

partecipa al forum

 

Segui le icone

 

Livelli di difficoltà

livello guruSolo per i "guru"!
livello avanzatoPer configuratori e sviluppatori
livello medioPer chi ha già familiarità
livello basePer tutti!

 

I video

video

Il documento è supportato da un video!