Operazioni sui file in Python

Operazioni sui file in Python

Questo articolo spiega le operazioni sui file in Python.

Tenendo presente "sicurezza", "efficienza" e "leggibilità", questa guida spiega i concetti passo dopo passo, dalle basi alle tecniche pratiche.

YouTube Video

Operazioni sui file in Python

Le operazioni sui file sono una competenza fondamentale essenziale, dai piccoli script alle applicazioni su larga scala.

Apertura e chiusura dei file

Per prima cosa, vediamo alcuni esempi di lettura e scrittura di file di testo. L'utilizzo dell'istruzione with (gestore di contesto) garantisce che il file venga chiuso correttamente.

Il seguente codice apre un file di testo, ne legge il contenuto e lo elabora linea per linea.

1# Open and read a text file safely using a context manager.
2with open("example.txt", "r", encoding="utf-8") as f:
3    # Read all lines lazily (as an iterator) and process each line.
4    for line in f:
5        print(line.rstrip())
  • Specificare esplicitamente encoding="utf-8" aiuta a ridurre i problemi dipendenti dalla piattaforma.
  • Anche con file di grandi dimensioni, for line in f è efficiente in termini di memoria.

Scrittura (sovrascrittura e aggiunta)

Presta attenzione alla modalità quando scrivi su un file. w serve per la sovrascrittura, a per l'aggiunta. Utilizza l'istruzione with anche in fase di scrittura.

Il seguente codice mostra esempi di base di sovrascrittura e aggiunta.

1# Write (overwrite) to a file.
2with open("output.txt", "w", encoding="utf-8") as f:
3    f.write("First line\n")
4    f.write("Second line\n")
5
6# Append to the same file.
7with open("output.txt", "a", encoding="utf-8") as f:
8    f.write("Appended line\n")
  • Questo codice scrive del testo su output.txt e poi aggiunge altro testo allo stesso file. La modalità "w" sovrascrive il file, mentre la modalità "a" aggiunge una nuova riga al termine del contenuto esistente.
  • Se hai bisogno di eseguire il flush, chiama flush(), ma di solito il contenuto viene svuotato automaticamente alla fine del contesto.
  • Se più processi o thread possono scrivere contemporaneamente, devi considerare il controllo esclusivo, come il file locking.

Lettura e scrittura di dati binari

Le immagini e i file compressi sono gestiti in modalità binaria (rb o wb). A differenza della modalità testo, encoding viene ignorato.

Il seguente codice legge un file binario e lo copia in un altro file in modalità binaria.

1# Read and write binary files.
2with open("image.png", "rb") as src:
3    data = src.read()
4
5with open("copy.png", "wb") as dst:
6    dst.write(data)
  • Quando gestisci file binari di grandi dimensioni, evita di leggere tutto in una volta con read(); leggere e scrivere a blocchi è più efficiente dal punto di vista della memoria.

Esempio di gestione di file di grandi dimensioni a blocchi

Per file enormi che non entrano in memoria, leggili e scrivili a blocchi di dimensione fissa. Poiché il vincolo è l'I/O, regolare la dimensione del buffer è utile.

Il seguente codice copia un file a blocchi di 64KB. Questo processo è rapido e mantiene basso l'utilizzo della memoria.

1# Copy a large file in chunks to avoid using too much memory.
2CHUNK_SIZE = 64 * 1024  # 64 KB
3
4with open("large_source.bin", "rb") as src,      open("large_dest.bin", "wb") as dst:
5    while True:
6        chunk = src.read(CHUNK_SIZE)
7        if not chunk:
8            break
9        dst.write(chunk)
  • Puoi regolare la dimensione del blocco in base alle caratteristiche della tua rete o del tuo disco. Su SSD, una dimensione del blocco leggermente superiore spesso funziona meglio.

Esempio di utilizzo dell'argomento buffering

Specificando l'argomento buffering nella funzione open(), puoi controllare la dimensione del buffer utilizzato internamente da Python. Questo ti permette di ottimizzare ulteriormente l'efficienza dell'input/output.

 1# Explicitly set the internal buffer size to 128 KB for faster I/O.
 2CHUNK_SIZE = 64 * 1024
 3BUFFER_SIZE = 128 * 1024  # 128 KB internal buffer
 4
 5with open("large_source.bin", "rb", buffering=BUFFER_SIZE) as src,      open("large_dest.bin", "wb", buffering=BUFFER_SIZE) as dst:
 6    while True:
 7        chunk = src.read(CHUNK_SIZE)
 8        if not chunk:
 9            break
10        dst.write(chunk)
  • Se imposti il valore di buffering a 0, l'I/O viene eseguito senza buffer; 1 abilita il buffering per linea, mentre valori pari a 2 o superiori usano un buffer della dimensione specificata in byte.
  • In generale, il valore predefinito è sufficiente perché il sistema operativo gestisce efficacemente la cache, ma modificare questo parametro può essere utile per file molto grandi o dispositivi particolari.

Operazioni di file moderne con Pathlib

Il modulo standard pathlib rende la gestione dei percorsi più intuitiva. Migliora la leggibilità e la sicurezza rispetto ai percorsi come stringhe.

 1from pathlib import Path
 2
 3path = Path("data") / "notes.txt"
 4
 5# Ensure parent directory exists.
 6path.parent.mkdir(parents=True, exist_ok=True)
 7
 8# Write and read using pathlib.
 9path.write_text("Example content\n", encoding="utf-8")
10content = path.read_text(encoding="utf-8")
11print(content)
  • Questo codice mostra come creare una directory con pathlib, scrivere su un file di testo e leggerne il contenuto. Con un oggetto Path, puoi gestire i percorsi in modo sia intuitivo che sicuro.
  • Path offre API comode come iterdir() e glob(). Puoi scrivere codice senza preoccuparti dei separatori di percorso tra diversi sistemi operativi.

File e directory temporanei (tempfile)

È possibile creare file temporanei in modo sicuro con tempfile. Questo evita condizioni di race di sicurezza e conflitti di nomi.

Il seguente codice mostra un esempio di creazione di dati temporanei usando un file temporaneo.

 1import tempfile
 2
 3# Create a temporary file that is deleted on close.
 4with tempfile.NamedTemporaryFile(
 5    mode="w+",
 6    encoding="utf-8",
 7    delete=True
 8) as tmp:
 9    tmp.write("temporary data\n")
10    tmp.seek(0)
11    print(tmp.read())
12
13# tmp is deleted here
  • Questo codice crea un file temporaneo, scrive e legge dati e lo elimina automaticamente alla fine del blocco with. Utilizzando tempfile.NamedTemporaryFile, puoi gestire file temporanei in modo sicuro e senza conflitti. Poiché è specificato delete=True, il file viene eliminato automaticamente.
  • Su Windows potresti non essere in grado di aprire il file da un altro handle immediatamente, quindi puoi impostare delete=False e gestire tu stesso l'eliminazione.

shutil: Operazioni di alto livello per copia, spostamento ed eliminazione

La copia, lo spostamento e l'eliminazione ricorsiva di file e directory è semplice con shutil.

 1import shutil
 2
 3# Copy a file preserving metadata.
 4shutil.copy2("source.txt", "dest.txt")
 5
 6# Move a file or directory.
 7shutil.move("old_location", "new_location")
 8
 9# Remove an entire directory tree (use with caution).
10shutil.rmtree("some_directory")
  • shutil.copy2 copia anche i metadata (come la data di modifica). move esegue comunque lo spostamento del file anche se la rinomina non può essere utilizzata.
  • rmtree è un'operazione pericolosa: conferma sempre ed effettua un backup prima di eliminare.

Metadati dei file (os.stat) e gestione delle autorizzazioni

La dimensione, la data di modifica e i permessi dei file possono essere letti e modificati con os e stat.

Il seguente codice ottiene le informazioni di base su un file con os.stat e modifica i permessi con os.chmod.

 1import os
 2import stat
 3from datetime import datetime
 4
 5st = os.stat("example.txt")
 6print("size:", st.st_size)
 7print("modified:", datetime.fromtimestamp(st.st_mtime))
 8
 9# Make file read-only for owner.
10os.chmod("example.txt", stat.S_IREAD)
  • I permessi si comportano in modo diverso su sistemi POSIX e Windows. Se la compatibilità cross-platform è importante, utilizza API di alto livello o aggiungi una gestione condizionale.

Blocco dei file (controllo esclusivo) — Differenze tra Unix e Windows

Il controllo esclusivo è necessario quando più processi accedono contemporaneamente allo stesso file. UNIX utilizza fcntl e Windows utilizza msvcrt.

Il seguente codice utilizza fcntl.flock su sistemi UNIX per acquisire un blocco esclusivo in fase di scrittura.

 1# Unix-style file locking example
 2import fcntl
 3
 4with open("output.txt", "a+", encoding="utf-8") as f:
 5    fcntl.flock(f, fcntl.LOCK_EX)
 6    try:
 7        f.write("Safe write\n")
 8        f.flush()
 9    finally:
10        fcntl.flock(f, fcntl.LOCK_UN)
  • Questo codice acquisisce un blocco esclusivo con fcntl.flock su sistemi UNIX-like, per scrivere in sicurezza e prevenire scritture simultanee sul file. Rilascia sempre il blocco dopo l'elaborazione per consentire l'accesso ad altri processi.
  • Su Windows, utilizza msvcrt.locking(). Per un utilizzo di livello superiore, considera librerie esterne come portalocker o filelock.

Pattern di scrittura atomica su file

Per evitare la corruzione dei file durante gli aggiornamenti, scrivi su un file temporaneo e sostituiscilo utilizzando os.replace in caso di successo.

Scrivere su un file temporaneo e poi sostituirlo evita corruzioni in caso di crash.

 1import os
 2from pathlib import Path
 3import tempfile
 4
 5def atomic_write(path: Path, data: str, encoding="utf-8"):
 6    # Write to a temp file in the same directory and atomically replace.
 7    dirpath = path.parent
 8    with tempfile.NamedTemporaryFile(
 9        mode="w",
10        encoding=encoding,
11        dir=str(dirpath),
12        delete=False
13    ) as tmp:
14        tmp.write(data)
15        tmp.flush()
16        tempname = tmp.name
17
18    # Atomic replacement (on most OSes)
19    os.replace(tempname, str(path))
20
21# Usage
22atomic_write(Path("config.json"), '{"key":"value"}\n')

os.replace esegue una sostituzione atomica all'interno dello stesso file system. Tieni presente che l'atomicità non è garantita tra montaggi diversi.

Accesso veloce con mmap (per dati di grandi dimensioni)

Per l'accesso casuale a file di grandi dimensioni, mmap migliora le prestazioni di I/O. Comporta principalmente operazioni binarie.

Il seguente codice mappa un file in memoria e legge o scrive intervalli specifici di byte. Presta attenzione quando modifichi la dimensione del file.

1import mmap
2
3with open("data.bin", "r+b") as f:
4    mm = mmap.mmap(f.fileno(), 0)  # map entire file
5    try:
6        print(mm[:20])  # first 20 bytes
7        mm[0:4] = b"\x00\x01\x02\x03"  # modify bytes
8    finally:
9        mm.close()
  • Questo codice mappa un file binario in memoria con mmap ed esegue operazioni di lettura/scrittura a livello di byte. La mappatura in memoria consente un rapido accesso casuale a grandi insiemi di dati.
  • mmap è efficiente, ma un uso scorretto può causare problemi di consistenza dei dati. Chiama flush() per sincronizzare quando necessario.

CSV / JSON / Pickle: Lettura e scrittura per formato

Formati di dati specifici dispongono di moduli dedicati. Usa csv per i file CSV, json per i JSON e pickle per salvare oggetti Python.

Il seguente codice fornisce esempi di base per la lettura e scrittura di CSV e JSON e l'utilizzo di Pickle. Poiché Pickle può eseguire codice arbitrario, evita di caricare dati da fonti non attendibili.

 1import csv
 2import json
 3import pickle
 4
 5# CSV write
 6with open("rows.csv", "w", encoding="utf-8", newline="") as f:
 7    writer = csv.writer(f)
 8    writer.writerow(["name", "age"])
 9    writer.writerow(["Alice", 30])
10
11# JSON write
12data = {"items": [1, 2, 3]}
13with open("data.json", "w", encoding="utf-8") as f:
14    json.dump(data, f, ensure_ascii=False, indent=2)
15
16# Pickle write (only for trusted environments)
17obj = {"key": "value"}
18with open("obj.pkl", "wb") as f:
19    pickle.dump(obj, f)
  • Specificare newline="" per i CSV è consigliato per evitare righe vuote aggiuntive su Windows. Con ensure_ascii=False, il JSON mantiene leggibili i caratteri UTF-8.

Lettura e scrittura diretta di file compressi (gzip / bz2 / zipfile)

La gestione diretta di gzip e zip può risparmiare spazio su disco. La libreria standard include i relativi moduli.

Il seguente codice è un semplice esempio di lettura e scrittura di file gzip come testo.

1import gzip
2
3# Write gzipped text.
4with gzip.open("text.gz", "wt", encoding="utf-8") as f:
5    f.write("Compressed content\n")
6
7# Read gzipped text.
8with gzip.open("text.gz", "rt", encoding="utf-8") as f:
9    print(f.read())
  • Esiste un compromesso tra il rapporto di compressione e la velocità a seconda del livello e del formato di compressione.

Misure di sicurezza e vulnerabilità

I seguenti punti possono essere considerati per le misure di sicurezza e vulnerabilità.

  • Non usare input non attendibili direttamente nei nomi o nei percorsi dei file.
  • Utilizza Pickle solo con fonti attendibili.
  • Minimizza i privilegi di esecuzione e concedi solo i permessi minimi ai processi che gestiscono file.
  • Usa file temporanei con tempfile e non salvare file semplici in directory pubbliche.

Se utilizzi input degli utenti nei percorsi dei file, sono necessarie normalizzazione e validazione. Ad esempio, utilizza Path.resolve() e controlla le directory superiori.

 1from pathlib import Path
 2
 3def safe_path(base_dir: Path, user_path: str) -> Path:
 4    candidate = (base_dir / user_path).resolve()
 5    if base_dir.resolve() not in candidate.parents and base_dir.resolve() != candidate:
 6        raise ValueError("Invalid path")
 7    return candidate
 8
 9# Usage
10base = Path("/srv/app/data")
11
12# Raises an exception: attempted path traversal outside `base`
13safe = safe_path(base, '../etc/passwd')
  • Fai particolare attenzione quando usi input esterni come percorsi di file in app web o API pubbliche.

Riepilogo dei pattern comuni

  • Usa sempre l'istruzione with (chiusura automatica).
  • Specificare esplicitamente l'encoding per i dati di testo.
  • Leggi e scrivi file di grandi dimensioni a blocchi.
  • Introdurre il file locking per le risorse condivise.
  • Per aggiornamenti critici, utilizzare il modello atomico 'scrivere su un file temporaneo → os.replace'.
  • Confermare sempre e creare backup prima di eseguire operazioni pericolose (come cancellazione o sovrascrittura).
  • Normalizzare e validare quando si utilizzano input esterni come percorsi di file.

Riepilogo

Per le operazioni sui file è importante utilizzare tecniche sicure ed affidabili come l'uso della dichiarazione with, la specifica esplicita della codifica e la scrittura atomica. Per l'elaborazione su larga scala o accessi paralleli, è necessario implementare sistemi di locking e gestione dei log per prevenire la corruzione e i conflitti dei dati. Bilanciare efficienza e sicurezza è la chiave per operazioni affidabili sui file.

Puoi seguire l'articolo sopra utilizzando Visual Studio Code sul nostro canale YouTube. Controlla anche il nostro canale YouTube.

YouTube Video