< Home
Stampa

Files

Sommario

Le operazioni sui files sono quelle che consentono a Python di accedere a risorse esterne, come il terminale, file, contenuti in rete, database, ecc. Python smette di essere quindi solo un linguaggio interattivo con l’utente ma consente di caricare e salvare dati, interagire col mondo esterno ed essere effettivamente produttivo.

Files di testo

Per aprire un file in Python il metodo più usato è il seguente:

with open('dati.txt') as f:
    contenuto = f.read()

Vediamo cosa succede:
– open apre il file e lo assegna alla variabile f;
– open richiede di indicare l’encoding, cioè ha bisogno di sapere che apriamo un file di testo;
– with è una istruzione che definisce un blocco all’interno del quale si opera sul file: al termine del blocco, with chiude automaticamente il file

Per modalità predefinita i files sono aperti in sola lettura e sono considerati file di testo. E’ possibile modificare il comportamento con i parametri mode ed encoding:
mode consente di aprire il file in scrittura, lettura e scrittura, creazione, ecc. e definisce anche il formato, se testo o binario;
encoding garantisce che un file di testo usi un determinato set di caratteri, di cui il più diffuso è utf-8, che però non è standard su tutti i sistemi operativi (ad esempio su Windows). Di norma quindi va indicato esplicitamente per consentire di convertire il file in una stringa di testo.

E’ possibile poi accedere ad una posizione specifica del file con la funzione f.seek(x) dove x è la posizione nel file.

Vediamo alcuni esempi concreti di lettura scrittura e seek:

# apre un file indicando sempre che è UTF-8
with open('dati.txt', encoding='utf-8') as f:
    print(f.read())

# 'r+': legge E scrive, ma il file DEVE esistere ──
with open('dati.txt', mode='r+', encoding='utf-8') as f:
    print(f.read())            # leggo il contenuto attuale
    f.write('Altro testo\n')  # aggiungo del testo

# 'w+': legge E scrive, CREA il file se manca (e lo svuota se esiste) ──
with open('nuovo.txt', mode='w+', encoding='utf-8') as f:
    f.write('prima riga\n').   # scrive nel file
    f.seek(0)                  # riporta la lettura all'inizio
    print(f.read())            # 'prima riga\n'

NOTA: negli esempi useremo with… ma nei progetti reali va sempre usata try-except prima di accedere ai file, per prevenire eccezioni come file non trovato o già in uso. Si veda la sezione sulle eccezioni in questa lezione.

Files binari

I files non di testo sono letti come files binari, cioè vengono memorizzati in memoria come sequenze di bytes. Questo rende possibile una loro elaborazione ad esempio tramite librerie esterne (es. manipolazione di immagini, PDF, audio, video, ecc.) o anche scritte dal programmatore.

Per poter memorizzare in memoria un file binario si usa un nuovo tipo di dato, bytes. Esso è una sequenza immutabile di bytes, e si comporta esattamente come una stringa, con cui condivide le caratteristiche:

Esso può anche essere definito a partire da una stringa, anteponendo il prefisso b:

b = b'\x00\x01\x42' # generato da numeri esadecimali
print(len(b)) # 3
sequence = b'ciao' # non è una stringa ma un elenco di bytes
print(sequence[0]) # 'c'

Vediamo i vari modi per crearla:

b1 = b'ciao'              # literal (la b è obbligatoria)
b2 = bytes([72, 105])     # da lista di interi → b'Hi'
b3 = bytes('ciao', 'utf-8')  # da str, con encoding esplicito → b'ciao'
b4 = b'\x00\xff\n'        # byte singoli con valori esadecimali (prefisso \x)
b5 = b''                  # bytes vuoto

Si possono gestire come le stringhe:

b = b'ciao mondo'

print(len(b))        # 12
print(b[0])          # 99      → int!
print(b[0:4])        # b'ciao' → bytes (le fette restano bytes)
print(b + b'!')      # b'ciao mondo!'  (concatenazione)
print(b.find(b'mondo'))  # 5   (ricerca di una sotto-sequenza)
print(b.replace(b'mondo', b'Python'))  # b'ciao Python'

for byte in b:       # col for estrae valori interi
    print(byte, end=' ')   # 99 105 97 111 ...

Possiamo convertire da bytes a str e viceversa:

testo = 'Caffè'          # str: caratteri Unicode
b = testo.encode('utf-8') # str → bytes (serve l'encoding)
print(b)                 # b'Caff\xe8'   ← l'accento richiede 2 byte in UTF-8

# bytes → str (serve lo stesso encoding)
print(b.decode('utf-8')) # 'Caffè'

Possiamo aprire files binari e salvarli in bytes in questo modo:

# ── Copiare un'immagine (l'esempio classico) ──
with open('foto.png', 'rb') as f:      # lettura binaria
    dati = f.read()                     # → bytes, non str

with open('copia_foto.png', 'wb') as f:  # scrittura binaria
    f.write(dati)

# ── Scrivere byte "a mano" (letterale b'...') ──
with open('dati.bin', 'wb') as f:
    f.write(b'\x00\x01\x02\n\x03\x04')

# ── Leggere: tutto, un byte alla volta, o riga per riga ──
with open('dati.bin', 'rb') as f:
    tutto = f.read()        # b'\x00\x01\x02\n\x03\x04'
    # f.seek(0)            # se si vuole tornare all'inizio

with open('dati.bin', 'rb') as f:
    primo = f.read(1)       # b'\x00'  bytes, non str!
    print(primo[0])         # 0        l'indice su bytes dà un int

with open('dati.bin', 'rb') as f:
    for riga in f:          # ogni "riga" è bytes, terminata da b'\n'
        print(riga)         # b'\x00\x01\x02\n' poi b'\x03\x04'

CSV

Un file CSV è un file di testo dove ogni riga è una tupla di valori separata da un carattere (ad esempio la virgola), con una riga di intestazione che indica l’etichetta per ogni valore. Ad esempio prendiamo questo testo (che salveremo come file inventario.csv):

prodotto;prezzo;quantità
laptop;900;5
mouse;25;10
tablet;450;8
monitor;200;3
tastiera;75;12
webcam;45;6
cuffie;30;15
docking station;120;4
SSD 1TB;95;7
memoria RAM 16GB;60;9
hub USB-C;35;14
sedia ergonomica;250;2

Il CSV è il formato principale per la condivisione di dati in forma tabellare, ad esempio fogli di calcolo Excel, o estrazione di tabelle di database. La loro manipolazione in Python passa attraverso due modalità: la lista di liste, oppure la lista di dizionari.

Python ha una libreria interna per gestire il caricamento delle singole righe in sotto forma di lista. Vediamo un esempio completo:

import csv

with open('inventario.csv', encoding='utf-8', newline='') as f:
    lettore = csv.reader(f)

    intestazione = next(lettore)  
    print(intestazione)               # ['nome', 'quantita', 'prezzo']

    print("prodotto -> valore")
    for riga in lettore:              # le righe rimanenti
        prodotto, prezzo, quantità = riga
        print(f'{prodotto} -> {int(quantità) * float(prezzo)}')

Vediamo le istruzioni:

  • csv.reader() crea un oggetto lettore, che funziona come una sequenza di elementi.
  • next(lettore): chiede alla sequenza di estrarre il prossimo valore, che sarà trasformato in lista. E’ la libreria csv a leggere la riga e produrre la lista dei valori
  • for riga in lettore: allo stesso modo vengono lette le singole righe, già come liste di valori
  • prodotto,prezzo,quantità = riga: svolge un unpacking degli elementi della lista in singole variabili

L’altro sistema è invece rappresentare la riga come un dizionario (dove le chiavi sono le etichette della prima riga). Possiamo usare la funzione DictReader, in questo caso anziché produrre una lista produce un dizionario per ogni riga (le chiavi le prende da solo dalla prima riga):

import csv

with open('inventario.csv', encoding='utf-8', newline='') as f:
    lettore = csv.DictReader(f)

    print("prodotto -> valore")
    for riga in lettore:              # le righe rimanenti
        totale = int(riga['quantità']) * float(riga['prezzo']) # calcola il totale
        print(f'{riga['prodotto']} -> {totale}')

Vediamo ora come scrivere un CSV. Ripartiamo dall’esempio precedente e trasferiamo però l’output su file:

import csv

with open('inventario.csv', encoding='utf-8', newline='') as f:
    lettore = csv.reader(f)
    next(lettore) 
    
    with open('esito.csv', 'w', encoding='utf-8', newline='') as e:
        scrittore = csv.writer(e)
        scrittore.writerow(["prodotto", "valore"])
        for riga in lettore:              # le righe rimanenti
            prodotto, prezzo, quantità = riga
            scrittore.writerow([prodotto, int(quantità) * float(prezzo)]) 

In questo caso apriamo prima il file in lettura ed il relativo lettore, poi il file in scrittura ed il relativo scrittore (avremo due with annidati per avere due file in contemporanea). La funzione chiave è writerow, che riceve come argomento una lista, e che appende in coda al csv di uscita come stringa di testo.

In modo analogo possiamo usare DictWriter.

import csv

with open('inventario.csv', encoding='utf-8', newline='') as f:
    lettore = csv.DictReader(f)

    with open('esito.csv', 'w', encoding='utf-8', newline='') as e:
        scrittore = csv.DictWriter(e, fieldnames=["prodotto", "valore"])
        scrittore.writeheader()  
        for riga in lettore:            
            totale = int(riga['quantità']) * float(riga['prezzo'])
            scrittore.writerow({"prodotto": riga['prodotto'], "valore": totale}) 

Rivediamo le due funzioni chiave:

  • Dictwriter(file, fieldnames): il primo attributo indica il file, il secondo i nomi dei campi del CSV (una lista)
  • writerow(data): in questo caso writerow accetta un dizionario non una lista

JSON

Il formato JSON (JavaScript Object Notation) è un formato testuale per rappresentare dizionari o array di dizionari, anche sotto forma di strutture annidate. Con un JSON è quindi possibile rappresentare liste o dizionari, che a loro volta possono contenere altre liste, dizionari o singoli valori (float, number o string).

E’ ad oggi il formato di interscambio dati più diffuso su Internet, offre rispetto al CSV una maggiore leggibilità ed espressività, ma occupa a parità di dati più spazio.

Passare da una lista/dizionario ad un JSON è una operazione quasi immediata con qualche piccolo accorgimento:

JSONPython
{ } oggettodict
[ ] arraylist
"testo" stringastr
423.14 numerointfloat
true / falseTrue / False
nullNone
undefinedNone

In pratica si tratta di convertire tipi nativi Javascript in tipi Python, sono quasi identici: JS ha un solo tipo number, mentre Python non supporta undefined (indica una variabile senza valore).

Quindi ad esempio:

[
  {"prodotto": "laptop", "prezzo": 900, "quantità": 5},
  {"prodotto": "mouse", "prezzo": 25, "quantità": 10},
  {"prodotto": "tablet", "prezzo": 450, "quantità": 8},
  {"prodotto": "monitor", "prezzo": 200, "quantità": 3},
  {"prodotto": "tastiera", "prezzo": 75, "quantità": 12},
  {"prodotto": "webcam", "prezzo": 45, "quantità": 6},
  {"prodotto": "cuffie", "prezzo": 30, "quantità": 15},
  {"prodotto": "docking station", "prezzo": 120, "quantità": 4},
  {"prodotto": "SSD 1TB", "prezzo": 95, "quantità": 7},
  {"prodotto": "memoria RAM 16GB", "prezzo": 60, "quantità": 9},
  {"prodotto": "hub USB-C", "prezzo": 35, "quantità": 14},
  {"prodotto": "sedia ergonomica", "prezzo": 250, "quantità": 2}
]

è praticamente già un oggetto Python, ma non va dimenticato che resta un file di testo. Lo apriamo con Python:

with open('inventario.json', encoding='utf-8') as f:
    inventario = json.load(f)      # dict

print(inventario['laptop']['prezzo'])   # 900
print(list(inventario.keys()))  

Possiamo scrivere in modo del tutto speculare:

with open('inventario.json', 'w', encoding='utf-8') as f:
    json.dump(inventario, f, ensure_ascii=False, indent=3)

Le due funzioni chiave sono:

  • json.load(file): esegue il parse del JSON;
  • json.dump(dict/list, file, ensure_ascii, indent): scrive su file, dove ensure_ascii a False permette di avere caratteri speciali come accenti, ed indent indica l’indentazione.

Conclusioni

Python ha una gestione dei file molto semplice, che consente di leggere e scrivere sia file di testo che binari (usando il tipo Bytes). E’ poi possibile, con le librerie csv e json, anche leggere e scrivere file CSV e JSON.