Edit: articolo del sito vecchio, recuperato e rigirato come post…
Questo piccolo articolo nasce dalla mia esperienza personale degli ultimi 3 mesi: costretto a casa dall’università a causa di un infortunio, mi sono dedicato alla programmazione (trascurando gli studi, ma questa è un’altra storia…), ed ora (8 gennaio 2006) sto scrivendo un programma in Python che avevo ancora quest’estate scritto in C, e vorrei discutere del mio passaggio tra questi due linguaggi.
Diciamo che la mia idea era innanzitutto di passare a programmare ad oggetti, in vista del corso del prossimo semestre di Ingegneria del software, in cui dovremo programmare in Java; avendo però qualche dubbio sulla programmazione in Java usando le librerie GTK per la parte grafica, e non avendo abbastanza banda (W la 56k!) per passare ad un costruttore di GUI in Swing stile Glade (per Eclipse), e certamente rifiutandomi di scrivere tutto il codice di GUI a mano, insomma mi sono affacciato a Python , di cui affascinano soprattutto le liste dinamiche inserite come tipo predefinito (in C sono un delirio di gestione, diventano un po’ più facili usando le glib, se Dio vuole, ma sono comunque ancora casiniste) ed il fatto che per la maggior parte delle cose ci sono moduli predefiniti semplicissimi da usare. Altra cosa importantissima, ha le eccezioni (costrutto try… except) che permette di intercettare molto facilmente gli errori, ed evita la maggior parte delle volte cose come errori di out of memory (avete presente quando un programma parte per la tangente, si freeza e vedete la CPU partire a palla al 100%, e l’unica soluzione è un bel kill… ecco, in Python è mooolto raro, in C è un festival!) e permette, essendo un linguaggio interpretato, di avere una fase di testing veloce e molto precisa, in C bisogna compilare con supporto di debug (flag -g al compilatore), usare gdb e non sempre vi viene dato un errore preciso (specie se sforate la memoria di un qualche vettore in un posto strano da trovare a mano). Infine, la cosa che mi ha dato una grossa spinta (quella definitiva) è stata la gestione delle stringhe.
Ammetto che in C ho dei grossi problemi con le stringhe. Tutto il resto bene o male funziona, ma la gestione delle stringhe è delirante. Non riesco, semplicemente, a lavorare. Esempio: ho costruito un’interfaccia grafica per convert di ImageMagick, inizialmente in C: in pratica permette di fare batch conversions di cartelle di immagini. Bene, la questione è che in C per gestire bene cose come i cambiamenti di estensione ai nomi ed il fatto che i nomi di files sono di lunghezza varia, uno pensa di usare puntatori a char e di usare free per liberarli… ebbene, da me non funziona assolutamente: la free mi dà continuamente errore, SEGFAULT a manetta, gdb a volte partiva per la tangente dandomi un fallback di 30 righe, tutti errori della glib o di gtk (probabilmente sforavo e invadevo la memoria di queste librerie) impossibili da seguire, impossibile capire dove. Il problema l’ho risolto facilmente: ho creato tutti vettori di stringhe di lunghezza prefissata, e tutto viene benissimo. Ma non è la sua. Poi ho cambiato linguaggio.
In 3 giorni (da 0 a Python) ho creato una versione di tale GUI perfettamente funzionante, con le stringhe dinamicamente allocate (tutto automatico) e molto meno codice. Sempre in GTK. I files di Glade (caricati ovviamente dinamicamente, in C con libglade ed in Python con pyGTK) uguali.
Scrivo oggi questo perchè sto scrivendo un programma che estrae l’audio da dvd, utile per creare mp3 da concerti live per esempio; quest’estate avevo fatto tutto in C, poi avevo mollato sempre per il discorso stringhe. Ora, in 2 settimane ho raggiunto un programma funzionante, seppur ancora indietro come implementazione (manca ad esempio una gestione di threads come si deve, al momento è tutto lineare).
Mi trovo ad essere molto d’accordo, in definitiva, con Eric S. Raymond (che tutti spero sappiate chi è), che scrisse nel 2000 un bellissimo articolo su LinuxJournal intitolato Why Python, in cui afferma ad un certo punto una cosa estremamente chiara e secondo me a ragion veduta: se non si devono fare programmi che siano kernel hacks o grafica 3D, è molto più comodo usare innanzitutto un linguaggio che abbia una gestione della memoria come si deve (in C e C++ come ben sappiamo non esiste ed è direi la sola ed unica causa di problemi in programmi scritti in tali linguaggi), poi si può tranquillamente usare un linguaggio interpretato, la velocità dei computer al giorno d’oggi non penalizza assolutamente un uso runtime di interpretazione e compilazione del linguaggio, per programmi che non appartengono ai tipi descritti sopra. Ho trovato anche una seconda visione come la mia, di John K. Ousterhout, addirittura ancor più vecchia, del 1998 (ma aggiornata poi nel 2001), in cui si afferma che programmi collante, tra GUI ed altri componenti, oggi nel 3° millennio sono usabili comodamente in linguaggi interpretati, mentre i linguaggi di alto livello (Java e C++ et similia) servono per le librerie che poi quegli interpreti usano, oppure in programmi che richiedono prestazioni di un certo tipo, magari per gli algoritmi complicati o grandi moli di dati. Per esempio, qualche mese fa avevo provato ad usare la zlib in C per fare un programma che comprimesse e decomprimesse files: la decompressione non ha assolutamente funzionato, la gestione è secondo me molto difficoltosa, bisogna leggere l’header scritto dalla libreria, per vedere le dimensioni iniziali e finali, anche lì problemi di gestione e codice di esempio mal commentato. In Python, in 10 minuti ho imparato a comprimere e decomprimere files, compreso il tempo servitomi a leggere la documentazione del modulo zipfile, senza contare il fatto che l’interprete a linea di comando è estremamente utile, permette di provare il codice letteralmente al volo, prima di scriverlo in uno script o un programma.
Torniamo ancora alla velocità, che può sembrare a molti difficile: se avete programmi di medio uso, che non necessitano di prestazioni, su un computer moderno non avranno problemi di alcun tipo! Uno script scritto qui al volo su un AMD Sempron 3000+ (1800 Mhz reali) si esegue in 5 secondi, compreso il tempo che impiega la CPU a salire da 800 Mhz (frequenza di riposo) a 1800:
from random import randint
n_min=0
n_max=1000
n_test=1000000
count=[]
total=0
med_min=n_max
med_max=n_max
for i in range(n_max):
count.append(0)
for i in range(n_test):
n=randint(n_min, n_max-1)
count[n]=count[n]+1
for i in range(n_max):
if count[i]>n_max and count[i]>med_max:
med_max=i
if count[i]<n_max and count[i]<med_min:
med_min=i
print “Sono state effettuate %d estrazioni, da %d a %d” % (n_test, n_min, n_max)
print “Il numero estratto meno volte (%d) e’ %d” % (count[med_min], med_min)
print “Il numero estratto piu’ volte (%d) e’ %d” % (count[med_max], med_max)
Questo codice estrae n_test volte i numeri di un intervallo stabilito, e ricerca la frequenza più alta e più bassa dell’estrazione. Può benissimo essere che il vettore iniziale possa essere preparato ed inizializzato in maniera più furba, in ogni caso queste 20 righe vengono eseguite in 5 secondi (circa): un tempo accettabile no? Una GUI costruita con Glade e caricata dinamicamente porta il programma ad un tempo di boot di mezzo secondo, se poi si suddividono i files di tali applicazioni in sottofiles (ad esempio uno per ogni finestra separata del programma) ecco che si riducono e distribuiscono i tempi di attesa (del tutto accettabili, secondo la mia attuale esperienza). Se poi pensiamo che Google fa un uso intensivo di Python ed è il motore di ricerca più veloce…
Al momento, questo è ciò che vi dico riguardo tale argomento, ognuno faccia le proprie riflessioni e veda cosa effettivamente gli conviene usare.