Next Up Previous Hi Index

Chapter 11

File ed eccezioni

Quando un programma è in esecuzione i suoi dati sono in memoria; nel momento in cui il programma termina o il computer viene spento tutti i dati in memoria vengono irrimediabilmente persi. Per conservare i dati devi quindi memorizzarli in un file, solitamente memorizzato su hard disk, floppy o CD-ROM.

Lavorando con un gran numero di file è logico cercare di organizzarli: questo viene fatto inserendoli in cartelle (dette anche "folder" o "directory"). Ogni file all'interno di una cartella è identificato da un nome unico.

Leggendo e scrivendo file i programmi possono scambiare informazioni e anche generare documenti stampabili usando il formato PDF o altri formati simili.

Lavorare con i file è molto simile a leggere un libro: per usarli li devi prima aprire e quando hai finito li chiudi. Mentre il libro è aperto lo puoi leggere o puoi scrivere una nota sulle sue pagine, sapendo in ogni momento dove ti trovi al suo interno. La maggior parte delle volte leggerai il libro in ordine, ma nulla ti vieta di saltare a determinate pagine facendo uso dell'indice.

Questa metafora può essere applicata ai file. Per aprire un file devi specificarne il nome e l'uso che intendi farne (lettura o scrittura).

L'apertura del file crea un oggetto file: nell'esempio che segue useremo la variabile f per riferirci all'oggetto file appena creato.

>>> f = open("test.dat","w")
>>> print f
<open file 'test.dat', mode 'w' at fe820>

La funzione open prende due argomenti: il primo è il nome del file ed il secondo il suo "modo". Il modo "w" significa che stiamo aprendo il file in scrittura.

Nel caso non dovesse esistere un file chiamato test.dat l'apertura in scrittura farà in modo di crearlo vuoto. Nel caso dovesse già esistere, la vecchia copia verrà rimpiazzata da quella nuova e definitivamente persa.

Quando stampiamo l'oggetto file possiamo leggere il nome del file aperto, il modo e la posizione dell'oggetto in memoria.

Per inserire dati nel file invochiamo il metodo write:

>>> f.write("Adesso")
>>> f.write("chiudi il file")

La chiusura del file avvisa il sistema che abbiamo concluso la scrittura e rende il file disponibile alla lettura:

>>> f.close()

Solo dopo aver chiuso il file possiamo riaprirlo in lettura e leggerne il contenuto. Questa volta l'argomento di modo è "r":

>>> f = open("test.dat","r")

Se cerchiamo di aprire un file che non esiste otteniamo un errore:

>>> f = open("test.cat","r")
IOError: [Errno 2] No such file or directory: 'test.cat'

Il metodo read legge dati da un file. Senza argomenti legge l'intero contenuto del file:

>>> Testo = f.read()
>>> print Testo
Adessochiudi il file

Non c'è spazio tra Adesso e chiudi perché non abbiamo scritto uno spazio tra le due stringhe al momento della scrittura.

read accetta anche un argomento che specifica quanti caratteri leggere:

>>> f = open("test.dat","r")
>>> print f.read(5)
Adess

Se non ci sono caratteri sufficienti nel file, read ritorna quelli effettivamente disponibili. Quando abbiamo raggiunto la fine del file read ritorna una stringa vuota:

>>> print f.read(1000006)
ochiudi il file
>>> print f.read()

>>>

La funzione che segue copia un file leggendo e scrivendo fino a 50 caratteri per volta. Il primo argomento è il nome del file originale, il secondo quello della copia:

def CopiaFile(Originale, Copia):
  f1 = open(Originale, "r")
  f2 = open(Copia, "w")
  while 1:
    Testo = f1.read(50)
    if Testo == "":
      break
    f2.write(Testo)
  f1.close()
  f2.close()
  return

L'istruzione break è nuova: la sua esecuzione interrompe immediatamente il loop saltando alla prima istruzione che lo segue (in questo caso f1.close()).

Il ciclo while dell'esempio è apparentemente infinito dato che la sua condizione ha valore 1 ed è quindi sempre vera. L'unico modo per uscire da questo ciclo è di eseguire un break che viene invocato quando Testo è una stringa vuota e cioè quando abbiamo raggiunto la fine del file in lettura.

11.1 File di testo

Un file di testo è un file che contiene caratteri stampabili e spazi bianchi, organizzati in linee separate da caratteri di ritorno a capo. Python è stato specificatamente progettato per elaborare file di testo e fornisce metodi molto efficaci per rendere facile questo compito.

Per dimostrarlo creeremo un file di testo composto da tre righe di testo separate da dei ritorno a capo:

>>> f = open("test.dat","w")
>>> f.write("linea uno\nlinea due\nlinea tre\n")
>>> f.close()

Il metodo readline legge tutti i caratteri fino al prossimo ritorno a capo:

>>> f = open("test.dat","r")
>>> print f.readline()
linea uno

>>>

readlines ritorna tutte le righe rimanenti come lista di stringhe:

>>> print f.readlines()
['linea due\n', 'linea tre\n']

In questo caso il risultato è in formato lista e ciò significa che le stringhe appaiono racchiuse tra apici e i caratteri di ritorno a capo come sequenze di escape.

Alla fine del file readline ritorna una stringa vuota e readlines una lista vuota:

>>> print f.readline()

>>> print f.readlines()
[]

Quello che segue è un esempio di elaborazione di un file: FiltraFile fa una copia del file Originale omettendo tutte le righe che iniziano con #:

def FiltraFile(Originale, Nuovo):
  f1 = open(Originale, "r")
  f2 = open(Nuovo, "w")
  while 1:
    Linea = f1.readline()
    if Linea == "":
      break
    if
Linea[0] == '#':
      continue
    f2.write(Linea)
  f1.close()
  f2.close()
  return

L'istruzione continue termina l'iterazione corrente e continua con il prossimo ciclo: il flusso di programma torna all'inizio del ciclo, controlla la condizione e procede di conseguenza.

Non appena Linea è una stringa vuota il ciclo termina grazie al break. Se il primo carattere di Linea è un carattere cancelletto l'esecuzione continua tornando all'inizio del ciclo. Se entrambe le condizioni falliscono (non siamo in presenza della fine del file né il primo carattere è un cancelletto) la riga viene copiata nel nuovo file.

11.2 Scrittura delle variabili

L'argomento di write dev'essere una stringa così se vogliamo inserire altri tipi di valore in un file li dobbiamo prima convertire in stringhe. Il modo più semplice è quello di usare la funzione str:

>>> x = 52
>>> f.write (str(x))

Un'alternativa è quella di usare l'operatore di formato %. Quando è applicato agli interi % è l'operatore modulo, ma quando il primo operando è una stringa % identifica l'operatore formato.

Il primo operando in questo caso è la stringa di formato ed il secondo una tupla di espressioni. Il risultato è una stringa formattata secondo la stringa di formato.

Cominciamo con un esempio semplice: la sequenza di formato "%d" significa che la prima espressione della tupla che segue deve essere formattata come un intero:

>>> NumAuto = 52
>>> "%d" % NumAuto
'52'

Il risultato è la stringa '52' che non deve essere confusa con il valore intero 52.

Una sequenza di formato può comparire dovunque all'interno di una stringa di formato così possiamo inserire un valore in una frase qualsiasi:

>>> NumAuto = 52
>>> "In luglio abbiamo venduto %d automobili." % NumAuto
'In luglio abbiamo venduto 52 automobili.'

La sequenza di formato "%f" formatta il valore nella tupla in un numero in virgola mobile e "%s" lo formatta come stringa:

>>> "Ricavo in %d giorni: %f milioni di %s." % (34,6.1,'euro')
'Ricavo in 34 giorni: 6.100000 milioni di euro.'

Per default la formattazione in virgola mobile %f stampa sei cifre decimali.

Il numero delle espressioni nella tupla deve naturalmente essere corrispondente a quello delle sequenze di formato nella stringa di formato, ed i tipi delle espressioni devono corrispondere a quelli delle sequenze di formato:

>>> "%d %d %d" % (1,2)
TypeError: not enough arguments for format string
>>> "%d" % 'euro'
TypeError: illegal argument type for built-in operation

Nel primo esempio la stringa di formato si aspetta tre espressioni ma nella tupla ne abbiamo passate solo due. Nel secondo vogliamo stampare un intero ma stiamo passando una stringa.

Per avere un miglior controllo del risultato possiamo modificare le sequenze di formato aggiungendo delle cifre:

>>> "%6d" % 62
'    62'
>>> "%12f" % 6.1
'    6.100000'

Il numero dopo il segno di percentuale è il numero minimo di caratteri che dovrà occupare il nostro valore dopo essere stato convertito. Se la conversione occuperà un numero di spazi minori verranno aggiunti spazi alla sinistra. Se il numero è negativo sono aggiunti spazi dopo il numero convertito:

>>> "%-6d" % 62
'62    '

Nel caso dei numeri in virgola mobile possiamo anche specificare quante cifre devono comparire dopo il punto decimale:

>>> "%12.2f" % 6.1
'        6.10'

In questo esempio abbiamo stabilito di creare una stringa di 12 caratteri con due cifre dopo il punto decimale. Questo tipo di formattazione è utile quando si devono stampare dei valori contabili in forma tabellare con il punto decimale allineato.

Immaginiamo un dizionario che contiene il nome (la chiave) e tariffa oraria (il valore) per una serie di lavoratori. Ecco una funzione che stampa il contenuto del dizionario in modo formattato:

def Report(Tariffe) :
  Lavoratori = Tariffe.keys()
  Lavoratori.sort()
  for Lavoratore in Lavoratori:
    print "%-20s %12.02f" % (Lavoratore, Tariffe[Lavoratore])

Per provare questa funzione creiamo un piccolo dizionario e stampiamone il contenuto:

>>> Tariffe = {'Maria': 6.23, 'Giovanni': 5.45, 'Alberto': 4.25}
>>> Report(Tariffe)
Alberto                      4.25
Giovanni                     5.45
Maria                        6.23

Controllando la larghezza di ciascun valore garantiamo che le colonne saranno perfettamente allineate, sempre che il nome rimanga al di sotto dei 20 caratteri e la tariffa oraria al di sotto delle 12 cifre...

11.3 Directory

Quando crei un nuovo file aprendolo e scrivendoci qualcosa, questo viene memorizzato nella directory corrente e cioè in quella dalla quale sei partito per eseguire l'interprete Python. Allo stesso modo se richiedi la lettura di un file Python lo cercherà nella directory corrente.

Se vuoi aprire il file da qualche altra parte dovrai anche specificare il percorso per raggiungerlo, e cioè il nome della directory di appartenenza:

>>>   f = open("/usr/share/dict/words","r")
>>>   print f.readline()
Aarhus

In questo esempio apriamo un file chiamato words che risiede in una directory chiamata dict che risiede in un'altra directory chiamata share che a sua volta risiede in usr. Quest'ultima risiede nella directory principale del sistema, /, secondo il formato Linux.

Non puoi usare / come parte di un nome di file proprio perché questo è un carattere delimitatore che viene inserito tra i vari nomi delle directory nella definizione del percorso.

11.4 Pickling

Abbiamo visto come per mettere dei valori in un file di testo li abbiamo dovuti preventivamente convertire in stringhe. Hai già visto come farlo usando str:

>>> f.write (str(12.3))
>>> f.write (str(4.567))
>>> f.write (str([1,2,3]))

Il problema è che quando cerchi di recuperare il valore dal file ottieni una stringa, e non l'informazione originale che avevi memorizzato. Oltretutto non c'è nemmeno il modo di sapere dove inizia o termina di preciso la stringa che definisce il valore nel file:

>>>   f.readline()
'12.34.567[1, 2, 3]'

La soluzione è il pickling (che letteralmente significa "conservazione sotto vetro") chiamato così perché "preserva" le strutture dei dati. Il modulo pickle contiene tutti i comandi necessari. Importiamo il modulo e poi apriamo il file nel solito modo:

>>> import pickle
>>> f = open("test.pck","w")

Per memorizzare una struttura di dati usa il metodo dump e poi chiudi il file:

>>> pickle.dump(12.3, f)
>>> pickle.dump(4.567, f)
>>> pickle.dump([1,2,3], f)
>>> f.close()

Puoi riaprire il file e caricare le strutture di dati memorizzati con il metodo load:

>>> f = open("test.pck","r")
>>> x = pickle.load(f)
>>> x
12.3
>>> type(x)
<type 'float'>
>>> x2 = pickle.load(f)
>>> x2
4.567
>>> type(x2)
<type 'float'>
>>> y = pickle.load(f)
>>> y
[1, 2, 3]
>>> type(y)
<type 'list'>

Ogni volta che invochiamo load otteniamo un singolo valore completo del suo tipo originale.

11.5 Eccezioni

Se il programma si blocca a causa di un errore in esecuzione viene creata un'eccezione: l'interprete si ferma e mostra un messaggio d'errore.

Le eccezioni più comuni per i programmi che hai visto finora possono essere:

In ogni caso il messaggio d'errore è composto di due parti: la categoria dell'errore e le specifiche separati dai due punti. Normalmente Python stampa la traccia del programma al momento dell'errore ma in questi esempi sarà omessa per questioni di leggibilità.

Molte operazioni possono generare errori in esecuzione ma in genere desideriamo che il programma non si blocchi quando questo avviene. La soluzione è quella di gestire l'eccezione usando le istruzioni try ed except.

Per fare un esempio possiamo chiedere ad un operatore di inserire il nome di un file per poi provare ad aprirlo. Se il file non dovesse esistere non vogliamo che il programma si blocchi mostrando un messaggio di errore; così cerchiamo di gestire questa possibile eccezione:

NomeFile = raw_input('Inserisci il nome del file: ')
try:
  f = open (NomeFile, "r")
except:
  print 'Il file', NomeFile, 'non esiste'

L'istruzione try esegue le istruzioni nel suo blocco. Se non si verificano eccezioni (e cioè se le istruzioni del blocco try sono eseguite senza errori) l'istruzione except ed il blocco corrispondente vengono saltate ed il flusso del programma prosegue dalla prima istruzione presente dopo il blocco except. Nel caso si verifichi qualche eccezione (nel nostro caso la più probabile è che il file richiesto non esiste) viene interrotto immediatamente il flusso del blocco try ed eseguito il blocco except.

Possiamo incapsulare questa capacità in una funzione: FileEsiste prende un nome di un file e ritorna vero se il file esiste, falso se non esiste.

def FileEsiste(NomeFile):
  try:
    f = open(NomeFile)
    f.close()
    return 1
  except:
    return 0

Puoi anche usare blocchi di except multipli per gestire diversi tipi di eccezioni. Vedi a riguardo il Python Reference Manual.

Con try/except possiamo fare in modo di continuare ad eseguire un programma in caso di errore. Possiamo anche "sollevare" delle eccezioni nel corso del programma con l'istruzione raise in modo da generare un errore in esecuzione quando qualche condizione non è verificata:

def InputNumero() :
  x = input ('Dimmi un numero: ')
  if x > 16 :
    raise 'ErroreNumero', 'mi aspetto numeri minori di 17!'
  return x

In questo caso viene generato un errore in esecuzione quando è introdotto un numero maggiore di 16.

L'istruzione raise prende due argomenti: il tipo di eccezione e l'indicazione specifica del tipo di errore. ErroreNumero è un nuovo tipo di eccezione che abbiamo inventato appositamente per questa applicazione.

Se la funzione che chiama InputNumero gestisce gli errori il programma continua, altrimenti Python stampa il messaggio d'errore e termina l'esecuzione:

>>> InputNumero()
Dimmi un numero: 17
ErroreNumero: mi aspetto numeri minori di 17!

Il messaggio di errore include l'indicazione del tipo di eccezione e l'informazione aggiuntiva che è stata fornita.

Esercizio: scrivi una funzione che usa InputNumero per inserire un numero da tastiera e gestisce l'eccezione ErroreNumero quando il numero non è corretto.

11.6 Glossario

File
entità identificata da un nome solitamente memorizzata su hard disk, floppy disk o CD-ROM, e contenente una serie di dati.
Directory
contenitore di file; è anche chiamata cartella o folder.
Percorso
sequenza di nomi di directory che specifica l'esatta locazione di un file.
File di testo
file che contiene solo caratteri stampabili organizzati come serie di righe separate da caratteri di ritorno a capo.
Istruzione break
istruzione che causa l'interruzione immediata del flusso del programma e l'uscita da un ciclo.
Istruzione continue
istruzione che causa l'immediato ritorno del flusso del programma all'inizio del ciclo senza completarne il corpo.
Operatore formato
operatore indicato da '%' che produce una stringa di caratteri in base ad una stringa di formato passata come argomento.
Stringa di formato
stringa che contiene caratteri stampabili e stabilisce come debbano essere formattati una serie di valori in una stringa.
Sequenza di formato
sequenza di caratteri che inizia con % e che stabilisce, all'interno di una stringa di formato, come debba essere convertito in stringa un singolo valore.
Pickling
operazione di scrittura su file di un valore assieme alla descrizione del suo tipo, in modo da poterlo recuperare facilmente in seguito.
Eccezione
errore in esecuzione.
Gestire un'eccezione
prevedere i possibili errori in esecuzione per fare in modo che questi non interrompano l'esecuzione del programma.
Sollevare un'eccezione
segnalare la presenza di una situazione anomala facendo uso dell'istruzione raise.


Next Up Previous Hi Index