Per permetterci di interagire con il filesystem, Python ci fornisce la funzione built-in open()
. Questa funzione può essere invocata per aprire un file e ritorna un file object. Quest'ultimo ci permette di eseguire diverse operazioni sul file, come ad esempio la lettura e la scrittura. Quando abbiamo finito di interagire con il file, dobbiamo infine ricordarci di chiuderlo, usando il metodo file.close()
.
La funzione open
La funzione open()
accetta diversi argomenti ma i due argomenti più importanti sono il nome del file che vogliamo aprire, e il modo di apertura.
Il nome del file deve essere una stringa che rappresenta un percorso in grado di identificare la posizione del file nel filesystem. Il percorso può essere relativo alla directory corrente (ad esempio 'file.txt'
, 'subdir/file.txt'
, '../file.txt'
, ecc.) o assoluto (ad esempio '/home/ezio/file.txt'
).
Il modo è opzionale, e il suo valore di default è la stringa 'r'
, cioè read (lettura). Questo vuol dire che se non specifichiamo il modo, Python aprirà il file in lettura. Se invece vogliamo poter scrivere sul file, possiamo specificare come modo la stringa 'w'
, cioè write (scrittura). Quando apriamo un file in scrittura, specificando quindi il modo 'w'
, possono succedere due cose: se il file non esiste, viene creato al percorso specificato; se esiste, il contenuto del file viene eliminato.
>>> open('test.txt') # il file non esiste, quindi Python dà errore (FileNotFoundError)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'test.txt'
>>>
>>> open('test.txt', 'w') # aprendolo in scrittura, il file viene creato
<_io.TextIOWrapper name='test.txt' mode='w' encoding='UTF-8'>
>>>
>>> open('test.txt', 'r') # ora che è stato creato, possiamo anche aprirlo in lettura
<_io.TextIOWrapper name='test.txt' mode='r' encoding='UTF-8'>
>>>
>>> open('test.txt') # se omettiamo il modo, il file viene aperto in lettura ('r')
<_io.TextIOWrapper name='test.txt' mode='r' encoding='UTF-8'>
Se vogliamo continuare ad aggiungere contenuto alla fine di un file, senza cancellare il contenuto esistente, possiamo usare la stringa 'a'
(append) come modo. Le stringhe 'r+'
e 'w+'
ci permettono di leggere e scrivere contemporaneamente (come 'w'
, anche 'w+'
elimina il contenuto del file). Il modo 'x'
(creazione esclusiva) crea e apre un nuovo file in scrittura, restituendo un errore (FileExistsError
) se il file esiste già.
Esistono infine due modi di aprire file: il modo testuale (usato per file di testo, come ad esempio file .txt
e .html
) e il modo binario (usato per file binari, come ad esempio immagini jpg
o audio .mp3
). Questi modi possono essere specificati aggiungendo alla stringa del modo una t
per i file testuali (default, quindi può essere omessa) e una b
per i file binari. Ad esempio per aprire un file binario in lettura possiamo usare 'rb'
, per aprire un file testuale in scrittura possiamo usare 'wt'
o semplicemente 'w'
.
Riassumendo:
Modalità | Descrizione |
---|---|
'r' |
Apre un file di testo in lettura. Modo di apertura di default dei file. |
'w' |
Apre un file di testo in scrittura. Se il file non esiste lo crea, altrimenti cancella il contenuto del file. |
'a' |
Apre un file di testo in append. Il contenuto viene scritto alla fine del file, senza modificare il contenuto esistente. |
'x' |
Apre un file di testo in creazione esclusiva. Se il file non esiste, restituisce un errore, altrimenti apre in scrittura cancellando il contenuto del file. |
'r+' |
Apre un file di testo in modifica. Permette di leggere e scrivere contemporaneamente. |
'w+' |
Apre un file di testo in modifica. Permette di leggere e scrivere contemporaneamente. Cancella il contenuto del file. |
Di default, questi modi vengono usati per aprire file testuali, e sono quindi equivalenti a 'rt'
, 'wt'
, 'at'
, 'xt'
, 'r+t'
, e 'w+t'
. Se invece vogliamo lavorare con file binari, è possibile aggiungere una 'b'
per specificare il modo binario, usando quindi 'rb'
, 'wb'
, 'ab'
, 'xb'
, 'r+b'
, e 'w+b'
.
I file object
Ora che abbiamo visto i diversi modi di invocare la funzione open()
, vediamo come possiamo interagire con i file object che restituisce.
I file object hanno diversi attributi e metodi:
>>> f = open('test.txt', 'w') # apriamo il file test.txt in scrittura
>>> f # open() ci restituisce un file object
<_io.TextIOWrapper name='test.txt' mode='w' encoding='UTF-8'>
>>> dir(f) # possiamo usare dir() per vedere l'elenco di attributi e metodi
[..., '_CHUNK_SIZE', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable',
'_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush',
'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline',
'readlines', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'writelines']
>>> f.name # l'attributo .name corrisponde al nome del file
'test.txt'
>>> f.mode # l'attributo .mode corrisponde al modo di apertura
'w'
>>> f.closed # l'attributo .closed è True se il file è stato chiuso, altrimenti False
False
>>> f.read # read è un metodo che, quando chiamato, legge e ritorna il contenuto
<built-in method read of _io.TextIOWrapper object at 0xb67cb734>
>>> f.write # write è un metodo che, quando chiamato, ci consente di scrivere nel file
<built-in method write of _io.TextIOWrapper object at 0xb67cb734>
>>> f.close # close è un metodo che, quando chiamato, chiude il file,
<built-in method close of _io.TextIOWrapper object at 0xb67cb734>
Vediamo alcuni esempi:
>>> f = open('test.txt', 'w') # apriamo il file in scrittura
>>> f.write('prima riga del file\n') # scriviamo una riga nel file
20
>>> f.write('seconda riga del file\n') # scriviamo un'altra riga nel file
22
>>> f.close() # chiudiamo il file
>>>
>>> f = open('test.txt') # riapriamo il file in lettura
>>> content = f.read() # leggiamo tutto il contenuto del file
>>> print(content)
prima riga del file
seconda riga del file
>>> f.close() # chiudiamo il file
I metodi file.read()
e file.write()
ci permettono di leggere e scrivere in un file. Il metodo file.read()
restituisce tutto il contenuto di un file come stringa (o byte string), ma è anche possibile passare come argomento un numero specifico di caratteri (o bytes). Il metodo file.write()
ci permette di aggiungere del contenuto al file e restituisce il numero di caratteri (o byte) scritti. In entrambi i casi è importante ricordarsi di chiudere il file usando il metodo file.close()
.
>>> # definiamo una lista di righe
>>> lines = [
... 'prima riga del file\n',
... 'seconda riga del file\n',
... 'terza riga del file\n',
... ]
>>> f = open('test.txt', 'w') # apriamo il file in scrittura
>>> f.writelines(lines) # usiamo il metodo writelines per scrivere le righe nel file
>>> f.close() # chiudiamo il file
>>>
>>> f = open('test.txt') # riapriamo il file in lettura
>>> f.readlines() # usiamo il metodo readlines per ottenere una lista di righe del file
['prima riga del file\n', 'seconda riga del file\n', 'terza riga del file\n']
>>> f.close() # chiudiamo il file
>>>
>>> f = open('test.txt') # riapriamo il file in lettura
>>> f.readline() # usiamo il metodo readline per ottenere una singola riga del file
'prima riga del file\n'
>>> f.readline() # usiamo il metodo readline per ottenere una singola riga del file
'seconda riga del file\n'
>>> f.readline() # usiamo il metodo readline per ottenere una singola riga del file
'terza riga del file\n'
>>> f.readline() # quando abbiamo letto tutto, il metodo restituisce una stringa vuota
''
>>> f.close() # chiudiamo il file
>>>
>>> # È possibile utilizzare un for per iterare sulle righe di un file:
>>> f = open('test.txt') # riapriamo il file in lettura
>>> for line in f: # iteriamo sulle righe del file
... line
...
'prima riga del file\n'
'seconda riga del file\n'
'terza riga del file\n'
>>> f.close() # chiudiamo il file
I metodi file.readlines()
e file.writelines()
possono essere usati per leggere e scrivere una lista di righe in un file. Il metodo file.readline()
ci permette di leggere una singola riga del file. Se vogliamo leggere il contenuto di un file riga per riga, possiamo semplicemente iterare sul file object usando un ciclo for
.
In questo esempio possiamo anche notare che quando abbiamo chiamato ripetutamente il metodo file.readline()
, abbiamo ottenuto righe consecutive, invece che ottenere 3 volte la prima riga. Ogni file object memorizza la posizione raggiunta durante la lettura e/o scrittura, e ogni operazione successiva riprende dallo stesso punto. Se eseguiamo letture successive, ogni lettura riprenderà dalla posizione memorizzata al termine della lettura precedente. Quando viene raggiunta la fine del file, le operazioni di lettura restituiscono una stringa vuota. È anche possibile utilizzare i metodifile.tell()
e file.seek()
per verificare e modificare la posizione memorizzata dal file object:
>>> # definiamo una lista di righe
>>> lines = [
... 'prima riga del file\n',
... 'seconda riga del file\n',
... 'terza riga del file\n',
... ]
>>> f = open('test.txt', 'w') # apriamo il file in scrittura
>>> f.writelines(lines) # usiamo il metodo writelines per scrivere le righe nel file
>>> f.seek(0, 0) # eseguiamo un seek per spostarci all'inizio del file (il secondo 0 indica l'inizio)
0
>>> f.write('PRIMA') # scriviamo 'PRIMA' all'inizio del file sovrascrivendo 'prima'
5
>>> f.seek(0, 2) # eseguiamo un seek per spostarci alla fine del file (il 2 indica la fine)
62
>>> f.write('quarta riga del file\n') # aggiungiamo una riga alla fine
21
>>> f.close() # chiudiamo il file
>>>
>>> f = open('test.txt') # riapriamo il file in lettura
>>> f.readline() # usiamo il metodo readline per ottenere una singola riga del file
'PRIMA riga del file\n'
>>> f.readline() # usiamo il metodo readline per ottenere un'altra riga del file
'seconda riga del file\n'
>>> f.tell() # vediamo che la posizione nel file è avanzata
42
>>> f.read() # usiamo il metodo read per leggere il resto del contenuto del file
'terza riga del file\nquarta riga del file\n'
>>> f.tell() # vediamo che la posizione nel file è avanzata ulteriormente
83
>>> f.read() # quando abbiamo letto tutto, il metodo restituisce una stringa vuota
''
>>> f.seek(0) # eseguiamo un seek per spostarci all'inizio del file
0
>>> f.tell() # vediamo che la posizione è ritornata a 0
0
>>> f.readlines() # rileggiamo l'intero contenuto del file come lista di stringhe
['PRIMA riga del file\n', 'seconda riga del file\n', 'terza riga del file\n', 'quarta riga del file\n']
>>> f.close() # chiudiamo il file
La seguente tabella riassume i metodi più comuni dei file object:
metodo | Descrizione |
---|---|
file.read() |
Legge e restituisce l'intero contenuto del file come una singola stringa. |
file.read(n) |
Legge e restituisce n caratteri (o byte). |
file.readline() |
Legge e restituisce una riga del file. |
file.readlines() |
Legge e restuisce l'intero contenuto del file come lista di righe (stringhe). |
file.write(s) |
Scrive nel file la stringa s e ritorna il numero di caratteri (o byte) scritti. |
file.writelines(lines) |
Scrive nel file la lista in righe lines . |
file.tell() |
Restituisce la posizione corrente memorizzata dal file object. |
file.seek(offset, pos) |
Modifica la posizione corrente memorizzata dal file object. |
file.close() |
Chiude il file. |
Il costrutto with
Abbiamo visto negli esempi precedenti, che ogni volta che apriamo un file è anche necessario invocare il metodo file.close()
per chiuderlo. Così facendo, non solo siamo costretti a ripetere la chiusura ogni volta, ma corriamo anche il rischio di dimenticarcene. Inoltre, se il programma viene interrotto a causa di un'eccezione, il file.close()
potrebbe non essere mai chiamato.
Per risolvere questi (e altri) problemi, in Python esiste il costrutto with
. Questo costrutto può essere usato con dei context manager (manager di contesti), cioè degli oggetti particolari che specificano delle operazioni che vanno eseguite all'entrata e all'uscita del contesto. I file object supportano il protocollo dei context manager, e possono quindi essere usati con il with
. Vediamo un esempio pratico:
>>> f = open('test.txt', 'w') # creiamo il file object
>>> with f: # usiamo il file object come context manager nel with
... f.write('contenuto del file') # scriviamo il file
... f.closed # verifichiamo che il file è ancora aperto
...
18
False
>>> f.closed # verifichiamo che dopo il with il file è chiuso
True
Nell'esempio possiamo notare che:
- La parola chiave
with
è seguita da un oggetto che supporta il protocollo dei context manager (in questo caso il file objectf
). - Dopo l'oggetto ci sono i due punti (
:
) e un blocco di codice indentato. - Prima di eseguire il blocco di codice indentato, il metodo speciale
f.__enter__()
viene chiamato (nel caso dei file non fa niente). - Il blocco di codice viene eseguito: all'interno del blocco il file è aperto e quindi possiamo scrivere sul file.
- Una volta eseguito il blocco di codice indentato, il metodo speciale
f.__exit__()
viene chiamato (nel caso dei file chiude il file). - Una volta terminato il
with
verifichiamo che il file è stato chiuso automaticamente (daf.__exit__()
).
In altre parole, usando il with
con i file object non dobbiamo più preoccuparci di chiudere il file. Esiste anche una forma più concisa per ottenere lo stesso risultato:
>>> with open('test.txt', 'w') as f:
... f.write('contenuto del file')
...
18
>>> f.closed
True
Questa forma del with
ci permette di creare l'oggetto direttamente e di assegnargli un nome dopo la keyword as
. Anche in questo caso, il with
chiamerà automaticamente f.__exit__()
che a sua volta chiamerà f.close()
e chiuderà il file automaticamente.
Il with
ci garantisce la chiusura del file anche nel caso in cui il programma venga interrotto da un'eccezione, e ci evita di dover ripetere f.close()
ogni volta. Il with
può inoltre essere usato anche con altri tipi di oggetti che supportano il protocollo dei context manager, ed è anche possibile definire nuovi oggetti di questo tipo.