Fondamenti di Programmazione

File

I file mantengono nella memeoria di un computer qualunque tipo di informazioni: testi, dati, immagini, video, musica, ecc. Python fornisce strumenti potenti e versatili per creare, leggere, scrivere e modificare file. Questi si basano sul tipo chiamato file. Un valore, cioè un oggetto di tipo file, rappresenta uno specifico file che può essere letto, scritto o modificato tramite i metodi del tipo file. In modo simile a come una lista può essere manipolata tramite i metodi del tipo list.

Apertura di File

Prima di poter fare una qualunque cosa con un file bisogna ottenere un oggetto di tipo file che lo rappresenta. Per fare ciò, si può usare la funzione built in open():

>>> help(open)
Help on built-in function open in module __builtin__:
open(...)
    open(name[, mode[, buffering]]) -> file object
    Open a file using the file() type, returns a file object.

Il primo parametro è obbligatorio ed è il nome o il percorso del file che si vuole aprire (ad es. 'myfile.txt'). Il terzo parametro non ci interessa. Il secondo mode indica la modalità di apertura:

mode significato
'r' apre un file esistente in lettura
'w' apre un file in scrittura, se non esiste lo crea vuoto,
se invece esiste ne cancella il contenuto.
'a' apre un file in append (ciò che si scrive è aggiunto alla
fine del file), se non esiste lo crea vuoto.

Se si aggiunge '+' (ad es. 'r+') il file è aperto in lettura e scrittura e se si aggiunge 'b' (ad es. 'rb' o 'r+b') è considerato un file binario, cioè, non di testo. Se mode non è specificato il suo valore di default è 'r'. Nel seguito consideriamo solamente file di testo. Oltre a queste modalità c'è anche 'rU' (o equivalentemente 'U') che è uguale a 'r' con un trattamento universale dei fine linea che possono essere differenti su diverse piattaforme ('\n' su Linux, '\r\n' su Windows e '\r' su Macintosh).

Per aprire un file se ne deve specificare il nome o il percorso. Se specifichiamo solamente il nome, il file è cercato nella directory detta current working directory, o più brevemente cwd.

f = open('myfile.txt')

Generalmente, la cwd è la directory in cui è contenuto il programma o la shell che esegue la funzione open(). Se vogliamo aprire un file che non è nella cwd, dobbiamo specificare il percorso del file o relativo o assoluto.

Un percorso relativo determina la locazione del file partendo dalla cwd. Il percorso e' formato dai nomi delle directory che contengono il file separati da un carattere separatore. su Linux e Mac OS X, il carattere separatore e' '/'. Su Windows e' tradizionalmente '\', anche se oggi giorno Windows accetta anche '/'.Ad esempio, 'docs/myfile.txt' il file è nella directory docs che è nella cwd, '../myfile.txt' il file è nella directory che contiene la cwd, '../../myfile.txt' il file è nella directory due livelli sopra la cwd, '../data/myfile.txt' il file è nella directory data che è nella directory che contiene la cwd.

Un percorso assoluto determina la locazione del file partendo dalla root directory. Per Linux e Mac OS X, as esempio, la root directory e' indicata con \. Ad esempio '/home/user/Documents/myfile.txt'. Su Windows si deve anche indicare il volume (o disco). Ad esempio 'C:\Users\user\Documents\myfile.txt'.

Si noti che per scrivere il backslash \ è necessario usare la sequenza di escape \\. In alternativa si può usare una stringa raw, facendo precedere la stringa con r, cioe' r\. Nelle stringhe raw le sequenze di escape non sono considerate.

Se si tenta di aprire in lettura un file che non esiste si ottiene un errore:

>>> f = open('prova.txt')
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    f = open('prova.txt')
IOError: [Errno 2] No such file or directory: 'prova.txt'

Se invece l'apertura va a buon fine, in lettura, in scrittura o entrambi, si potrà elaborare il contenuto del file usando i metodi del tipo file. Quando si è terminato di usare il file, è bene chiuderlo, tramite il metodo close(),

f.close()

di modo che Python possa recuperare le risorse che servivano per tenere aperto il file.

Quando si lavora con i file è preferibile usare il costrutto with che gestisce in modo automatico la chiusura del file (anche se si dovesse verificare un errore durante l'elaborazione):

with open(nome_file) as f:
    <instruzioni>

e' approssimativalemente equivalente a

f = open(name_file)
<istruzioni>
f.close()

Lettura di File

Come file d'esempio usiamo Alice's Adventures in Wonderland di Lewis Carrol che si può scaricare da Project Gutenberg (la versione in UTF-8). Prima di tutto lo apriamo in lettura. Per Linux e Mac OS X è meglio usare la modalità 'U' perché il file ha i fine linea nella versione Windows.

>>> path = 'dati/'
>>> f = open(path + 'alice.txt', 'rU')

Possiamo leggere l'intero file tramite il metodo read():

>>> testo = f.read()
>>> len(testo)
177424

e così possiamo subito scoprire che il file ha 177424 caratteri. Possiamo stampare un piccolo estratto:

>>> print testo[5900:6200]
CHAPTER I

[Sidenote: _Down the Rabbit-Hole_]

ALICE was beginning to get very tired of sitting by her
sister on the bank, and of having nothing to do: once or 
twice she had peeped into the book her sister was reading, 
but it had no pictures or conversations in it, "and what 
is the use of a book,"

Se proviamo a chiamare ancora il metodo read() otteniamo

 >>> f.read()
 ''

cioè la stringa vuota, perché la prima read() ha letto l'intero file e non è rimasto nessun carattere da leggere. Il metodo read() può prendere un parametro opzionale che determina il numero massimo di caratteri da leggere. Il metodo readline() permette di leggere una linea alla volta. Per provarlo dobbiamo chiudere il file e riaprirlo:

>>> f.close()
>>> f = open(path + 'alice.txt', 'U')
>>> f.readline()
"Project Gutenberg's Alice's Adventures in Wonderland, by Lewis
 Carroll\n"
>>> f.readline()
'\n'
>>> f.readline()
'This eBook is for the use of anyone anywhere at no cost and 
with\n'
>>> f.readline()
'almost no restrictions whatsoever.  You may copy it, give it 
away or\n'

La prima chiamata legge la prima linea, la seconda legge la seconda linea e così via. Si osservi che viene letto anche il fine linea. Il metodo readlines() legge tutte le linee rimaste ancora da leggere fino alla fine del file:

>>> f.close()
>>> f = open(path + 'alice.txt', 'U')
>>> linee = f.readlines()
>>> len(linee)
4046

e così scopriamo che ci sono 4096 linee. Possiamo inoltre facilmente accedere ad una qualsiasi di queste:

>>> linee[100]
'            "There will be nonsense in it!"--\n'
>>> linee[200]
'          The Mock Turtle drew a long breath and\n'

C'è anche un altro modo per scandire le linee di un file. Lo possiamo vedere nel seguente esempio di una funzione che prende in input il nome di un file e una stringa e ritorna la lista dei numeri di linea in cui la stringa appare:

def lines(name, s):
    f = open(name, 'U')
    linee = []
    i = 1
    for linea in f:
        if linea.find(s) != -1:
            linee.append(i)
        i += 1
    f.close()
    return linee

Gli oggetti di tipo file possono essere iterati come una sequenza di linee. Usando questa funzione possiamo ottenere:

>> linee = lines(path + 'alice.txt', 'Turtle')
>>> linee
[201, 2457, 2598, 2600, 2602, 2616, 2636, 2643, 2649, 2656, 
2657, 2661, 2666, 2668, 2672, 2678, 2685, 2688, 2696, 2700, 
2704, 2711, 2716, 2731, 2733, 2741, 2747, 2756, 2768, 2783, 
2787, 2801, 2810, 2814, 2822, 2828, 2834, 2838, 2845, 2873, 
2879, 2885, 2887, 2926, 2931, 2936, 2939, 2946, 2957, 2962, 
2988, 2994, 2999, 3020, 3030, 3032, 3034, 3037, 3060, 3615, 
3625]
>>> linee = lines(path + 'alice.txt', 'Alice')
>>> len(linee)
400

In casi come questo in cui durante l'iterazione dei valori di una sequenza abbiamo bisogno anche degli indici si può usare la funzione built in enumerate() che itera sulle coppie indice, valore:

def lines(nome, s):
    f = open(nome, 'U')
    linee = []
    for i, linea in enumerate(f):
        if linea.find(s) != -1:
            linee.append(i + 1)
    f.close()
    return linee

Quando si lavora con i file è preferibile usare il costrutto with che gestisce in modo automatico la chiusura del file (anche se si dovesse verificare un errore durante l'elaborazione):

def lines(nome, s):
    with open(nome, 'U') as f:
        linee = []
        for i, linea in enumerate(f):
            if linea.find(s) != -1:
                linee.append(i + 1)
    return linee

Nel blocco di istruzioni del with (cioè, le istruzioni indentate di almeno 4 spazi dopo il with), il file è aperto è il relativo oggetto è il valore della variabile f, non appena l'esecuzione esce dal blocco il file è automaticamente chiuso.

Elaborare File di Testo

Se vogliamo creare una lista di tutte le parole in un file di testo come possiamo fare? Per parola intendiamo una qualsiasi sequenza di caratteri alfabetici (maiuscoli o minuscoli) di lunghezza massimale. Potremmo usare il metodo split() delle stringhe se potessimo sostituire (tramite il metodo replace()) tutti i caratteri non alfabetici con il carattere spazio. Per prima cosa allora vediamo di determinare tutti i caratteri non alfabetici presenti in una stringa. Possiamo usare il metodo isalpha() delle stringhe che ritorna True se la stringa a cui è applicato contiene solamente caratteri alfabetici:

def noalpha(s):
    noa = ''
    for c in s:
        if not (c in noa or c.isalpha()):
            noa += c
    return noa

if __name__ == '__main__':
    def test(s):
        print s
        print 'Non alfabetici:', '"'+noalpha(s)+'"'
        print

    test("Frase (con parentesi [],{}, simboli vari %&#@), 
          numeri 0987 e puntegg.!")
    test("FraseSenzaCaratteriNonAlfabetici")

La linea if __name__ == '__main__': serve a far sì che la parte di codice dentro l'if è eseguita solamente quando il modulo è eseguito direttamente, quando invece il modulo è importato in un'altro tale parte di codice non è eseguita. Questo funziona perchè la variabile speciale __name__, quando il modulo è eseguito direttamente ha valore 'main', mentre quando è importato ha valore uguale al nome del modulo stesso. Grazie a ciò possiamo scrivere il codice di testing all'intero del modulo stesso e possiamo comunque importare il modulo senza che il codice di testing venga eseguito.

A questo punto possiamo scrivere la nostra funzione che ritorna la lista di tutte le parole:

import noalpha as na

def words(nomefile):
    with open(nomefile, 'U') as f:
        text = f.read()
    noa = na.noalpha(text)
    for c in noa:
        text = text.replace(c, ' ')
    return text.split()

if __name__ == '__main__':
    def test(nomefile):
        print 'File:', '"'+nomefile+'"'
        lstw = words(nomefile)
        print 'Numero parole:', len(lstw)
        print 'Alcune parole:', lstw[1000:1040]
        print

    test('alice.txt')

Eseguendolo scopriamo che ci sono 31385 parole.

La lista prodotta contiene tutte le occorrenze delle parole comprese tutte le ripetizioni. Se invece vogliamo conoscere le parole distinte, senza ripetizioni? Ovviamente lo possiamo fare costruendo a partire dalla lista originale un'altra lista in cui aggiungiamo solamente le parole che non sono già presenti. Però Python ci permette di farlo in modo ancora più semplice, come illustreremo nella lezione successiva.

Scrittura di File

In Python, si possono scrivere dati su file aprendo il file in scrittura con modelita' 'w'. Ogni volta che si fa questo, il file viene sovrascitto, cioe' si perde in contenuto precedente. Per scrivere il testo testo su un file si puo' usare il metodo write(testo). Un'altro modo e' quello di scrivere una lista di linee di testo su un file usando il metodo writelines(linee). As esempio, per scrivere 'contenuto del file' sul file 'testo.txt' si puo' fare

>>> with open('testo.txt','w') as f:
        f.write('contenuto del file')