Come copiare un file in Python

La copia di file in Python è un’operazione comune e, fortunatamente, il linguaggio offre strumenti robusti e versatili per gestirla. In questa guida tecnica, esploreremo i vari approcci per eseguire un’operazione di python copy file, partendo dalla copia di singoli file fino ad arrivare a scenari più complessi come la copia ricorsiva di directory.

Modulo shutil: La Scelta Professionale per la Copia di File e Directory

Quando si tratta di operazioni su file e directory di alto livello, il modulo shutil (shell utilities) è il tuo migliore amico. Offre funzioni che semplificano notevolmente compiti che, altrimenti, richiederebbero molto più codice.

1. Copia di un Singolo File: shutil.copy()

La funzione shutil.copy(src, dst) è l’opzione più semplice e comune per copiare un file. Copia il contenuto del file sorgente (src) nel file o directory di destinazione (dst).

  • src: Il percorso del file sorgente da copiare.
  • dst: Il percorso della destinazione. Può essere un file (il nome del nuovo file) o una directory (in tal caso, il file verrà copiato al suo interno mantenendo il nome originale).

Caratteristiche:

  • Copia i permessi del file sorgente.
  • Non copia i metadati come l’ora dell’ultima modifica (usa shutil.copy2() per questo).
  • Se dst è una directory, il file verrà copiato al suo interno con lo stesso nome.
  • Se dst esiste ed è un file, verrà sovrascritto. Se è una directory, il file sorgente verrà copiato al suo interno.

Esempio Pratico: Copia un file da una posizione all’altra.

Python

import shutil
import os

# Creiamo un file di test
with open("sorgente.txt", "w") as f:
    f.write("Questo è il contenuto del file sorgente.")

# 1. Copia il file sorgente in un nuovo file
try:
    shutil.copy("sorgente.txt", "destinazione.txt")
    print("File 'sorgente.txt' copiato in 'destinazione.txt'")
except Exception as e:
    print(f"Errore durante la copia del file: {e}")

# 2. Creiamo una directory di destinazione
os.makedirs("cartella_destinazione", exist_ok=True)

# 3. Copia il file sorgente nella directory di destinazione
try:
    shutil.copy("sorgente.txt", "cartella_destinazione/")
    print("File 'sorgente.txt' copiato in 'cartella_destinazione/'")
except Exception as e:
    print(f"Errore durante la copia del file nella cartella: {e}")

# Pulizia
os.remove("sorgente.txt")
os.remove("destinazione.txt")
os.remove("cartella_destinazione/sorgente.txt")
os.rmdir("cartella_destinazione")

2. Copia di un Singolo File con Metadati: shutil.copy2()

La funzione shutil.copy2(src, dst) è identica a shutil.copy() ma con un’aggiunta fondamentale: copia anche i metadati del file, inclusa l’ora dell’ultima modifica, l’ora di accesso e i flag.

Esempio Pratico: Dimostrare la copia dei metadati.

Python

import shutil
import os
import time

# Creiamo un file di test e modifichiamolo per avere un timestamp significativo
with open("sorgente_meta.txt", "w") as f:
    f.write("Contenuto per test metadati.")
time.sleep(1) # Aspetta un secondo per avere un timestamp diverso da creazione
os.utime("sorgente_meta.txt", (time.time(), time.time())) # Aggiorna ora di accesso e modifica

# Ottieni l'ora di modifica originale
original_mtime = os.path.getmtime("sorgente_meta.txt")
print(f"Ora di modifica originale: {time.ctime(original_mtime)}")

# Copia con shutil.copy()
shutil.copy("sorgente_meta.txt", "destinazione_copy.txt")
copy_mtime = os.path.getmtime("destinazione_copy.txt")
print(f"Ora di modifica con copy(): {time.ctime(copy_mtime)}")
print(f"I metadati sono gli stessi con copy(): {original_mtime == copy_mtime}\n")

# Copia con shutil.copy2()
shutil.copy2("sorgente_meta.txt", "destinazione_copy2.txt")
copy2_mtime = os.path.getmtime("destinazione_copy2.txt")
print(f"Ora di modifica con copy2(): {time.ctime(copy2_mtime)}")
print(f"I metadati sono gli stessi con copy2(): {original_mtime == copy2_mtime}")

# Pulizia
os.remove("sorgente_meta.txt")
os.remove("destinazione_copy.txt")
os.remove("destinazione_copy2.txt")

Come si può notare dall’output, shutil.copy() crea un nuovo file con un timestamp di modifica “fresco”, mentre shutil.copy2() conserva quello del file sorgente.

3. Copia Ricorsiva di Directory: shutil.copytree()

Questa è la funzione per eccellenza quando hai bisogno di copiare un’intera struttura di directory, inclusi tutti i suoi file e sottodirectory.

  • shutil.copytree(src, dst, ...): Copia ricorsivamente l’intera directory src nella directory dst.
    • src: La directory sorgente da copiare.
    • dst: La directory di destinazione. Non deve esistere prima di chiamare copytree. Se esiste, verrà generato un errore.
    • dirs_exist_ok=False (default): Se True, la directory di destinazione può esistere, e i contenuti saranno uniti (ma i file esistenti saranno sovrascritti). Attenzione: Questa opzione è stata aggiunta in Python 3.8. Per versioni precedenti, devi gestire l’esistenza della directory manualmente.
    • ignore (opzionale): Una funzione richiamabile che permette di escludere file o directory dalla copia (simile a .gitignore).

Esempio Pratico: Copiare una struttura di cartelle completa.

Python

import shutil
import os

# Creiamo una struttura di directory di test
os.makedirs("sorgente_dir/sottocartella", exist_ok=True)
with open("sorgente_dir/file1.txt", "w") as f:
    f.write("File 1")
with open("sorgente_dir/sottocartella/file2.txt", "w") as f:
    f.write("File 2")

# Copia ricorsiva della directory
try:
    # Se 'destinazione_dir' esiste, la rimuoiviamo prima per evitare errori nelle versioni < 3.8
    if os.path.exists("destinazione_dir"):
        shutil.rmtree("destinazione_dir")

    shutil.copytree("sorgente_dir", "destinazione_dir")
    print("Directory 'sorgente_dir' copiata ricorsivamente in 'destinazione_dir'")

    # Verifichiamo il contenuto (opzionale)
    print("Contenuto di 'destinazione_dir':")
    for root, dirs, files in os.walk("destinazione_dir"):
        level = root.replace("destinazione_dir", '').count(os.sep)
        indent = ' ' * 4 * (level)
        print(f'{indent}{os.path.basename(root)}/')
        subindent = ' ' * 4 * (level + 1)
        for f in files:
            print(f'{subindent}{f}')

except Exception as e:
    print(f"Errore durante la copia ricorsiva: {e}")

# Pulizia
shutil.rmtree("sorgente_dir")
shutil.rmtree("destinazione_dir")

Copia Ricorsiva con Esclusione di File/Directory:

Supponiamo di voler copiare una directory ma escludere tutti i file .log e una sottocartella specifica.

Python

import shutil
import os

# Creiamo una struttura di directory più complessa
os.makedirs("progetto_sorgente/data", exist_ok=True)
os.makedirs("progetto_sorgente/logs", exist_ok=True) # Questa cartella verrà esclusa
with open("progetto_sorgente/config.ini", "w") as f: f.write("config")
with open("progetto_sorgente/data/user.csv", "w") as f: f.write("data")
with open("progetto_sorgente/logs/app.log", "w") as f: f.write("log data")
with open("progetto_sorgente/temp.log", "w") as f: f.write("temp log")

def ignore_patterns(path, names):
    ignored = []
    # Escludi tutti i file .log
    for name in names:
        if name.endswith('.log'):
            ignored.append(name)
    # Escludi la directory 'logs'
    if 'logs' in names:
        ignored.append('logs')
    print(f"Ignoro in {path}: {ignored}")
    return ignored

try:
    if os.path.exists("progetto_destinazione"):
        shutil.rmtree("progetto_destinazione")

    shutil.copytree("progetto_sorgente", "progetto_destinazione", ignore=ignore_patterns)
    print("\nCopia ricorsiva con pattern di esclusione completata.")

    print("\nContenuto della destinazione (dovrebbe mancare 'logs/' e i file .log):")
    for root, dirs, files in os.walk("progetto_destinazione"):
        level = root.replace("progetto_destinazione", '').count(os.sep)
        indent = ' ' * 4 * (level)
        print(f'{indent}{os.path.basename(root)}/')
        subindent = ' ' * 4 * (level + 1)
        for f in files:
            print(f'{subindent}{f}')

except Exception as e:
    print(f"Errore durante la copia con esclusione: {e}")

# Pulizia
shutil.rmtree("progetto_sorgente")
if os.path.exists("progetto_destinazione"):
    shutil.rmtree("progetto_destinazione")

4. Copia a Basso Livello: open() e Lettura/Scrittura

Per scenari molto specifici, o se si vuole un controllo granulare sul processo di copia, è possibile copiare il file leggendo il contenuto in blocchi e scrivendolo. Questo è ciò che le funzioni di shutil fanno “sotto il cofano”.

Questo approccio è utile per:

  • Copiare file molto grandi per evitare di caricare l’intero contenuto in memoria.
  • Implementare logica di copia personalizzata (es. visualizzare una barra di progresso, crittografare durante la copia).

Esempio Pratico: Copia un file in blocchi.

Python

import os

def copy_file_manual(src_path, dst_path, buffer_size=4096):
    """
    Copia un file leggendolo e scrivendolo in blocchi.
    """
    try:
        with open(src_path, 'rb') as fsrc: # 'rb' per lettura in modalità binaria
            with open(dst_path, 'wb') as fdst: # 'wb' per scrittura in modalità binaria
                while True:
                    buffer = fsrc.read(buffer_size)
                    if not buffer:
                        break
                    fdst.write(buffer)
        print(f"File '{src_path}' copiato manualmente in '{dst_path}'")
    except FileNotFoundError:
        print(f"Errore: Il file sorgente '{src_path}' non trovato.")
    except Exception as e:
        print(f"Errore durante la copia manuale del file: {e}")

# Creiamo un file di test più grande
with open("large_sorgente.bin", "wb") as f:
    f.write(os.urandom(1024 * 1024 * 5)) # 5 MB di dati casuali

copy_file_manual("large_sorgente.bin", "large_destinazione.bin")

# Pulizia
os.remove("large_sorgente.bin")
os.remove("large_destinazione.bin")

L’uso della modalità binaria ('rb', 'wb') è cruciale per garantire che tutti i byte del file vengano copiati correttamente, specialmente per file non testuali (immagini, video, eseguibili).

Considerazioni Finali sul “Python Copy File”

  • Gestione degli Errori: In tutti gli esempi, è fondamentale includere blocchi try-except per gestire potenziali IOError (es. permessi negati, disco pieno, file non trovato) o altre eccezioni.
  • Sovrascrittura: shutil.copy() e shutil.copy2() sovrascrivono i file di destinazione se esistono. shutil.copytree() genera un errore se la directory di destinazione esiste (a meno che non si usi dirs_exist_ok=True in Python 3.8+).
  • Simbolici Link: shutil.copy() e shutil.copy2() copiano il contenuto di un link simbolico, non il link stesso. Per copiare il link come link, si può usare shutil.copy(src, dst, follow_symlinks=False).
  • Sicurezza: Fai attenzione quando copi file da fonti non fidate, specialmente in posizioni sensibili del sistema, poiché potresti inavvertitamente sovrascrivere file importanti.

In generale, per la maggior parte delle esigenze di python copy file, le funzioni del modulo shutil sono la scelta consigliata per la loro semplicità, efficienza e gestione integrata di molti dettagli che altrimenti dovrebbero essere gestiti manualmente.