20: SALVARE E CARICARE DATI SU DISCO

IL TIPO DI DATI file

Una comune operazione compiuta da molti programmi che usiamo sul computer è il salvataggio ed il caricamento di dati dal disco rigido. Queste funzionalità sono supportate da tutti i linguaggi di programmazione, che in genere implementano un file system, cioè un insieme di istruzioni atte a questo scopo.

In Python esiste un tipo di dato apposito, il tipo file: una volta creata una variabile di questo tipo, potremo usarla per le nostre operazioni.

LA FUNZIONE open()

Per creare un oggetto di tipo file è necessaria la funzione open(): essa va chiamata con due argomenti: il primo è una stringa che contiene il nome del file che vogliamo aprire, il secondo (opzionale) indica le operazioni che vogliamo compiere sul file. Proviamo con IDLE:


>>> f = open("miofile.tmp", "w")

Se eseguite questa riga di codice ed andate poi ad esplorare la directory nella quale è installato Python (in Windows dovrebbe essere C:\Python34 o C:\Programmi\Python34) dovreste trovare il file miofile.tmp creato da Python.

Il significato della seconda stringa è il seguente:

Valore Significato
"w" write Apre un file in scrittura: se il file con il nome indicato non è presente sul disco ne crea uno, altrimenti cancella i contenuti del file e li sovrascrive con i dati che scriveremo
"r" read Apre un file in lettura. Il file con il nome indicato deve essere già presente sul disco, altrimenti si ottiene un FileNotFoundError
"a" append Apre un file per aggiungere dati: se il file con il nome indicato non è presente sul disco ne crea uno, altrimenti scriverà i nuovi dati alla fine del file, senza cancellare quelli vecchi
"r+" read - write Apre un file sul quale potremo sia scrivere che leggere. Anche in questo caso il file deve essere già esistente, o si ottiene un errore

il secondo argomento è opzionale ed il suo valore di default è "r" (in modo da non cancellare accidentalmente un file se ce lo dimentichiamo!).

Purtroppo la trattazione non finisce qui, perchè ai valori indicati possiamo aggiungere una "b" alla fine ("rb", "wb", "ab", "r+b") dove la b significa binary. Il significato di questa b è piuttosto oscuro e dovuto al fatto che Windows, Linux e Mac scrivono e leggono le stringhe con formati diversi. In pratica non dovremo mettere la b se intendiamo scrivere e leggere soltanto stringhe (modalità testo), mentre dovremo metterla se intendiamo scrivere e leggere dati di altro tipo (modalità binaria). Anche così è però probabile che una stringa scritta da un computer Windows non sia poi leggibile da un Mac. Se tutto questo vi sembra troppo complicato, sappiate che per fortuna Python ha trovato un modo per aggirare il problema, che spiegheremo alla fine, e che quindi potete per il momento non preoccuparvi se non avete capito. In ogni caso nel prossimo paragrafo mostreremo alcune semplici operazioni di scrittura e lettura di stringhe, quindi apriremo i file in modalità testo.

LEGGERE E SCRIVERE STRINGHE

Una volta aperto il file, il nostro programma vedrà l'oggetto file come uno stream (flusso di caratteri), cioè una specie di grossa stringa nella quale leggere o scrivere caratteri in sequenza. All'interno di questo stream le varie stringhe sono delimitate dal carattere di ritorno a capo "\n" e si può leggerle e scriverle mediante le seguenti funzioni, che usano tutte la sintassi con il punto (nella tabella seguente f è un oggetto di tipo file, che supponiamo già creato con la open()).

Funzione Significato
f.read() restituisce l'intero contenuto del file sotto forma di una sola stringa. Se il file era composto da più stringhe, esse saranno separate dal carattere di ritorno a capo "\n"
f.read(n) come la precedente, ma legge al massimo n caratteri (se il file è più lungo si ferma)
f.readline() legge e restituisce la prossima stringa dal file (cioè legge il file solo fino al prossimo carattere "\n"). Se siamo arrivati alla fine del file restituisce la stringa nulla.
f.readline(n) legge e restituisce la prossima stringa, fino ad un massimo di n caratteri
f.write(str) scrive una stringa nel file. Se vogliamo terminare la stringa è necessario aggiungere alla fine il carattere "\n", altrimenti la prossima scrittura sarà la continuazione di questa stringa. Restituisce il numero di caratteri scritti.
f.close() chiude il file. E' sempre bene chiudere i file appena abbiamo finito di leggere o scrivere, in quanto essi richiedono risorse del sistema operativo (in ogni caso all'uscita dal programma tutti i file vengono chiusi automaticamente). Dopo aver chiuso un oggetto file non possiamo più usarlo per leggere o scrivere (otterremmo un errore).

Vediamo ora di fare qualche esempio in IDLE: ricordatevi che i nostri esempi lasceranno poi come "spazzatura" il nostro file di prova nella directory di IDLE, quindi eventualmente cercatelo e cancellatelo.

Per prima cosa creiamo un file e scriviamoci qualcosa (notiamo che quando usiamo la write() IDLE ci risponde come al solito scrivendo il valore restituito, cioè il numero di caratteri scritti nel file):


>>> f = open("miofile.tmp", "w")            # file aperto in scrittura
>>> f.write("Voglio tanto bene a mamma\n")  # ritorno a capo alla fine della stringa

26

>>> f.write("Ma veramente tanto! ")         # senza ritorno a capo

20

>>> f.write("Tanto tanto!\n")

13

>>> f.close()
>>> f.write("Non posso piu' scrivere!")

Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    f.write("Non posso piu' scrivere!")
ValueError: I/O operation on closed file.

Alla fine ho volutamente provocato un errore cercando di scrivere dopo la chiusura. Ora riapriamo il nostro file in lettura e rileggiamo quello che vi avevamo scritto:


>>> f = open("miofile.tmp", "r")
>>> f.read()

'Voglio tanto bene a mamma\nMa veramente tanto! Tanto tanto!\n'

Notiamo che la seconda e la terza write() hanno in realtà scritto una sola stringa 'Ma veramente tanto! Tanto tanto!\n', perchè nella prima delle due non avevamo aggiunto il ritorno a capo. Se ora proviamo ancora a leggere dal file, otterremo delle stringhe vuote, perchè abbiamo raggiunto la fine del file (Inglese: EOF, end of file). Se vogliamo leggere le stringhe una alla volta dobbiamo chiudere e riaprire:


readline()                        # ho gia' letto tutto, non posso piu' leggere

''
                           
>>> f.close()                     # chiudo e riapro
>>> f = open("miofile.tmp", "r")
>>> f.readline()

'Voglio tanto bene a mamma\n'

>>> f.readline()

'Ma veramente tanto! Tanto tanto!\n'

Sorge ora una domanda: cosa dovrei fare se volessi salvare dei numeri (interi o float)? Un semplice metodo è quello di convertirli in stringhe con la funzione str(). Vediamo un esempio:


>>> f.close()
>>> f = open("miofile.tmp", "a")      # apre il file per aggiungere dati alla fine
>>> f.write(str(10)+ "\n")            # scrive il numero 10 sotto forma di stringa

3

>>> f.close()                         # chiudiamo il file ...
>>> f = open("miofile.tmp", "r")      # ... e lo riapriamo per leggere
>>> f.readline()                      # le due righe gia' scritte ...

'Voglio tanto bene a mamma\n'

>>> f.readline()

'Ma veramente tanto! Tanto tanto!\n'

>>> n = int(f.readline())             # ,,, e il nosto numero (riconvertito da stringa ad intero)
>>> n

10

Questo metodo è comunque piuttosto farraginoso: se dovessimo salvare un oggetto piuttosto complesso come una lista (o una lista di liste) ci servirebbero molte istruzioni di conversione. Inoltre rimane il fatto accennato prima che ad esempio un computer Mac potrebbe non leggere le stringhe scritte da un computer Windows.

IL MODULO pickle

Per risolvere questi problemi i programmatori di Python hanno deciso di intraprendere una via piuttosto radicale, cioè di salvare i dati su disco in un formato proprio di Python. Le funzioni per leggere e scrivere in questo formato sono contenute nel modulo pickle. Le più utilizzate sono le seguenti:

Funzione Significato
dump(obj, file) scrive la variabile obj nel file file, che deve essere stato aperto in modalità binaria. obj può essere una variabile di qualsiasi tipo (stringa, numero, lista ...)
load(file) restituisce un oggetto letto dal file file (anch'esso aperto in modalità binaria)

Queste due funzioni ci semplificano notevolmente la vita. Basterà ricordarsi di aprire i file in modalità binaria ("wb", "rb", "ab", "r+b"), dopodichè potremo salvare o caricare qualsiasi cosa con una singola istruzione. Notate anche che esse, a differenza di quelle precedenti, non usano la sintassi con il punto e vanno chiamate mettendo il nome del file tra gli argomenti.

Vediamo un'applicazione: riprendiamo il nostro vecchio gioco indovina_numerp.py e modifichiamolo in modo che ricordi il record ed il nome di chi l'ha stabilito:


from random import *
from pickle import *

seed()

try:
    f = open("records.dat", "rb")     # apre il file "records.dat"
    record = load(f)                  # carica il record
    nome = load(f)                    # carica il nome del giocatore
    f.close()
except FileNotFoundError:
    record = 1000000000               # se il file non esiste inizializza come prima
    nome = ""                   
risp = "s"                            # da qui il programma prosegue come prima                             
while risp == "s":
    num = randrange(1, 21)
    tentativi = 0
    print ("Ho pensato un numero da 1 a 20. Prova ad indovinarlo!")
    mio_num = -1
    while mio_num != num:
        tentativi += 1
        mio_num = int(input("??? "))
        if mio_num < num:
            print("Troppo basso")
        elif mio_num > num:
            print("Troppo alto")
    print("Hai indovinato! Hai impiegato", tentativi, "tentativi")
    if tentativi < record:
        print("Hai battuto il record!")
        record = tentativi
        nome = input("Inserisci il tuo nome: ")
                                        # chiede anche il nome del giocatore
    risp = input("Vuoi giocare ancora (s/n)? ")
                                        # uscita dal programma
f = open("records.dat", "wb")           # riapre il file in scrittura ...
dump(record, f)                         # ... salva il record ...
dump(nome, f)                           # ... ed il nome
f.close()

Soffermiamoci un attimo sulle prime righe: notate che l'istruzione open() è inserita in una try: questo è un comune stratagemma per gestire la possibilità che il file non esista (ad esempio la prima volta che si lancia il programma). Se il file non esiste verrà eseguita la except ed inizializzeremo le nostre variabili record e nome come facevamo prima, altrimenti le caricheremo da file. Alla fine del programma riapriamo lo stesso file in scrittura e salviamo nuovamente le due variabili, in modo che esse siano disponibili la prossima volta che lanciamo il programma.

ESERCIZIO 20.1 Applicate lo stesso procedimento al programma gioco_porte.py

Fine della lezione