Come usare os.walk in Python per esplorare ricorsivamente una directory

os.walk di Python è uno strumento potente per esplorare ricorsivamente una directory e i suoi contenuti. Utilizzando questa funzione, è possibile ottenere facilmente tutte le sottodirectory e i file di una directory specificata. In questo articolo, esploreremo dall’uso di base di os.walk a esempi pratici, in modo da rendere più efficienti i compiti che coinvolgono operazioni sulle directory.

Indice

Che cos’è os.walk


os.walk è una funzione inclusa nel modulo standard os di Python, che esplora ricorsivamente una directory specificata e genera un elenco dei file e delle sottodirectory all’interno di quella directory. Usando questa funzione, è possibile esplorare facilmente strutture di directory complesse, rendendo molto semplice ottenere elenchi di file e cartelle.

Come funziona os.walk


os.walk funziona come un generatore e restituisce un tuple contenente i seguenti 3 elementi.

  1. Percorso della directory (dirpath)
    Il percorso della directory attualmente esplorata.
  2. Elenco delle sottodirectory (dirnames)
    Elenco dei nomi delle sottodirectory nella directory corrente.
  3. Elenco dei file (filenames)
    Elenco dei nomi dei file nella directory corrente.

Caratteristiche

  • Esplorazione ricorsiva: Esplora automaticamente le sottodirectory della directory specificata.
  • Ordine: È possibile configurare il trattamento della gerarchia delle directory in modalità top-down o bottom-up (topdown=True/False).
  • Efficienza: Genera informazioni al volo, quindi è molto efficiente in termini di memoria.

Usi

  • Ricerca di nomi di file
  • Creazione di un elenco di file con un’estensione specifica
  • Calcolo delle dimensioni delle sottodirectory
  • Automatizzazione di attività di backup o spostamento

Uso di base

Utilizzando os.walk, è facile ottenere file e cartelle all’interno di una directory specificata. Ecco un esempio di codice di base.

Esempio di codice

import os

# Specificare la directory di destinazione
target_directory = "/path/to/your/directory"

# Esplorare la directory utilizzando os.walk
for dirpath, dirnames, filenames in os.walk(target_directory):
    print(f"Percorso attuale: {dirpath}")
    print(f"Sottodirectory: {dirnames}")
    print(f"File: {filenames}")
    print("-" * 40)

Esempio di output


Immagina che la struttura delle directory sia la seguente:

/path/to/your/directory
├── file1.txt
├── file2.txt
├── subdir1
│   └── file3.txt
└── subdir2
    └── file4.txt

In questo caso, l’esecuzione di os.walk restituirà il seguente output:

Percorso attuale: /path/to/your/directory
Sottodirectory: ['subdir1', 'subdir2']
File: ['file1.txt', 'file2.txt']
----------------------------------------
Percorso attuale: /path/to/your/directory/subdir1
Sottodirectory: []
File: ['file3.txt']
----------------------------------------
Percorso attuale: /path/to/your/directory/subdir2
Sottodirectory: []
File: ['file4.txt']
----------------------------------------

Spiegazione

  • dirpath: Percorso della directory attualmente esplorata.
  • dirnames: Elenco delle sottodirectory nella directory attuale.
  • filenames: Elenco dei file nella directory attuale.

Punti di attenzione


os.walk genera un errore se la directory specificata non esiste, quindi è consigliabile verificare prima se la directory esiste.

Gestione dei file e delle directory separati

Usando os.walk, puoi facilmente separare file e cartelle all’interno di una directory. Se desideri applicare operazioni diverse a ciascuno di essi, puoi semplicemente aggiungere delle istruzioni condizionali.

Esempio di codice


Ecco un esempio che separa i file dalle sottodirectory e applica operazioni diverse su ciascuno:

import os

# Specificare la directory di destinazione
target_directory = "/path/to/your/directory"

# Esplorare la directory e separare i file dalle sottodirectory
for dirpath, dirnames, filenames in os.walk(target_directory):
    # Operazioni sulle sottodirectory
    for dirname in dirnames:
        subdir_path = os.path.join(dirpath, dirname)
        print(f"Directory: {subdir_path}")

    # Operazioni sui file
    for filename in filenames:
        file_path = os.path.join(dirpath, filename)
        print(f"File: {file_path}")

Esempio di output


Supponiamo che la struttura della directory sia la seguente:

/path/to/your/directory
├── file1.txt
├── file2.txt
├── subdir1
│   └── file3.txt
└── subdir2
    └── file4.txt

L’output sarà il seguente:

Directory: /path/to/your/directory/subdir1
Directory: /path/to/your/directory/subdir2
File: /path/to/your/directory/file1.txt
File: /path/to/your/directory/file2.txt
File: /path/to/your/directory/subdir1/file3.txt
File: /path/to/your/directory/subdir2/file4.txt

Spiegazione del codice

  • os.path.join: Unisce dirpath con dirname o filename per creare il percorso assoluto.
  • Elaborazione delle directory (for dirname in dirnames): È possibile applicare operazioni specifiche sulle directory, come ottenere la data di creazione.
  • Elaborazione dei file (for filename in filenames): È possibile applicare operazioni specifiche sui file, come ottenere la dimensione del file.

Esempi avanzati

  • Creare e gestire un elenco di sottodirectory.
  • Estrazione e elaborazione di file che seguono una determinata convenzione di nomi.
  • Filtrare i file in base a dimensioni o data di creazione.

Ricerca di file con una specifica estensione

Utilizzando os.walk, è facile cercare file con una determinata estensione. Ad esempio, è possibile estrarre file con estensioni come .txt o .jpg in modo molto efficiente.

Esempio di codice


Ecco un esempio di codice che cerca file con estensione .txt e stampa il loro percorso:

import os

# Specificare la directory di destinazione
target_directory = "/path/to/your/directory"

# Specificare l'estensione da cercare
target_extension = ".txt"

# Cercare i file con l'estensione specificata
for dirpath, dirnames, filenames in os.walk(target_directory):
    for filename in filenames:
        if filename.endswith(target_extension):
            file_path = os.path.join(dirpath, filename)
            print(f"Trovato: {file_path}")

Esempio di output


Se la struttura delle directory è la seguente:

/path/to/your/directory
├── file1.txt
├── file2.doc
├── subdir1
│   └── notes.txt
└── subdir2
    └── image.png

L’output sarà:

Trovato: /path/to/your/directory/file1.txt
Trovato: /path/to/your/directory/subdir1/notes.txt

Spiegazione del codice

  • filename.endswith(target_extension): Restituisce True se il nome del file termina con l’estensione specificata. Questo viene usato per filtrare i file in base al loro formato.
  • os.path.join: Viene usato per generare il percorso completo del file.

Ricerca di file con più estensioni


Se si desidera cercare file con più estensioni, è possibile modificare la condizione in questo modo.

# Specificare le estensioni da cercare in una lista
target_extensions = [".txt", ".doc"]

for dirpath, dirnames, filenames in os.walk(target_directory):
    for filename in filenames:
        if filename.endswith(tuple(target_extensions)):
            file_path = os.path.join(dirpath, filename)
            print(f"Trovato: {file_path}")

Esempi avanzati

  • Recuperare un elenco di file di codice sorgente (ad esempio, file .py) in un progetto.
  • Cercare e processare file in formati di immagine specifici (ad esempio, .jpg o .png).
  • Creare statistiche sui file in base alle loro estensioni.

Esplorazione con limitazione della profondità della directory

os.walk esplora per impostazione predefinita tutte le sottodirectory in modo ricorsivo. Tuttavia, a volte è necessario esplorare solo fino a una determinata profondità. In tal caso, è possibile monitorare la profondità corrente e limitare l’esplorazione a un certo livello.

Esempio di codice


Ecco un esempio che limita l’esplorazione alla profondità 2:

import os

# Specificare la directory di destinazione
target_directory = "/path/to/your/directory"

# Specificare la profondità massima di esplorazione
max_depth = 2

# Esplorazione con limitazione della profondità
for dirpath, dirnames, filenames in os.walk(target_directory):
    # Calcolare la profondità corrente
    current_depth = dirpath.count(os.sep) - target_directory.count(os.sep) + 1

    if current_depth > max_depth:
        # Se la profondità è maggiore del limite, saltare le sottodirectory
        del dirnames[:]  # Rimuovere tutte le sottodirectory
        continue

    print(f"Profondità {current_depth}: {dirpath}")
    print(f"Sottodirectory: {dirnames}")
    print(f"File: {filenames}")
    print("-" * 40)

Esempio di output


Supponiamo che la struttura delle directory sia la seguente:

/path/to/your/directory
├── file1.txt
├── subdir1
│   ├── file2.txt
│   └── subsubdir1
│       └── file3.txt
└── subdir2
    └── file4.txt

Con una limitazione alla profondità 2, l’output sarà:

Profondità 1: /path/to/your/directory
Sottodirectory: ['subdir1', 'subdir2']
File: ['file1.txt']
----------------------------------------
Profondità 2: /path/to/your/directory/subdir1
Sottodirectory: ['subsubdir1']
File: ['file2.txt']
----------------------------------------
Profondità 2: /path/to/your/directory/subdir2
Sottodirectory: []
File: ['file4.txt']
----------------------------------------

Spiegazione del codice

  • os.sep: Restituisce il separatore di percorso dipendente dal sistema operativo (ad esempio \\ su Windows, / su Unix).
  • dirpath.count(os.sep): Conta il numero di separatori di percorso in dirpath per calcolare la profondità corrente.
  • del dirnames[:]: Svuota la lista delle sottodirectory per evitare che vengano esplorate ulteriormente.

Esempi avanzati

  • Esplorare solo le directory di livello superiore in un progetto di grandi dimensioni.
  • Visualizzare una parte della struttura ad albero con limiti di profondità.
  • Ridurre il carico di lavoro durante l’esplorazione su disco limitando la profondità di ricerca.

Ignorare i file o le cartelle nascoste

Quando esplori una directory, potrebbe essere utile ignorare i file o le cartelle nascoste (che di solito iniziano con un punto .). Con os.walk, puoi filtrare facilmente questi elementi per un’elaborazione più efficiente.

Esempio di codice


Ecco un esempio che ignora i file e le cartelle nascoste durante l’esplorazione:

import os

# Specificare la directory di destinazione
target_directory = "/path/to/your/directory"

# Esplorazione ignorando file e cartelle nascoste
for dirpath, dirnames, filenames in os.walk(target_directory):
    # Ignorare le cartelle nascoste
    dirnames[:] = [d for d in dirnames if not d.startswith(".")]

    # Ignorare i file nascosti
    visible_files = [f for f in filenames if not f.startswith(".")]

    print(f"Percorso attuale: {dirpath}")
    print(f"Sottodirectory: {dirnames}")
    print(f"File: {visible_files}")
    print("-" * 40)

Esempio di output


Se la struttura delle directory è la seguente:

/path/to/your/directory
├── file1.txt
├── .hidden_file.txt
├── subdir1
│   ├── file2.txt
│   └── .hidden_folder
│       └── file3.txt
└── subdir2
    └── file4.txt

Ignorando i file e le cartelle nascoste, l’output sarà:

Percorso attuale: /path/to/your/directory
Sottodirectory: ['subdir1', 'subdir2']
File: ['file1.txt']
----------------------------------------
Percorso attuale: /path/to/your/directory/subdir1
Sottodirectory: []
File: ['file2.txt']
----------------------------------------
Percorso attuale: /path/to/your/directory/subdir2
Sottodirectory: []
File: ['file4.txt']
----------------------------------------

Spiegazione del codice

  • Ignorare le cartelle nascoste (dirnames[:] = ...): Viene sovrascritta la lista dirnames per escludere le cartelle che iniziano con un punto (.), impedendo l’esplorazione di tali directory.
  • Ignorare i file nascosti ([f for f in filenames ...]): Usando la comprensione delle liste, si escludono i file che iniziano con un punto (.) dalla lista dei file.

Punti di attenzione

  • Se i file o le cartelle sono impostati come “nascosti” tramite attributi di sistema (soprattutto su Windows), il codice sopra non li rileverà. In tal caso, è necessario usare moduli aggiuntivi (ad esempio ctypes).

Esempi avanzati

  • Escludere file di configurazione nascosti (come .gitignore o .env) durante l’elaborazione.
  • Creare un elenco di directory visibili per gli utenti, escludendo le cartelle nascoste.
  • Durante la pulizia di grandi set di dati, escludere gli elementi nascosti.

Esempio pratico: Calcolare la dimensione di una directory

Con os.walk, è possibile sommare le dimensioni di tutti i file all’interno di una directory specifica per calcolare la dimensione totale della directory. Questo è utile per determinare quanto spazio di archiviazione una cartella sta occupando.

Esempio di codice


Ecco un esempio di codice per calcolare la dimensione di una directory:

import os

# Specificare la directory di destinazione
target_directory = "/path/to/your/directory"

def calculate_directory_size(directory):
    total_size = 0
    # Esplorare la directory
    for dirpath, dirnames, filenames in os.walk(directory):
        for filename in filenames:
            file_path = os.path.join(dirpath, filename)
            try:
                # Aggiungere la dimensione del file
                total_size += os.path.getsize(file_path)
            except FileNotFoundError:
                # Gestire l'eccezione se il file è stato eliminato
                pass
    return total_size

# Calcolare la dimensione della directory
directory_size = calculate_directory_size(target_directory)

# Stampare il risultato (in byte e MB)
print(f"Dimensione della directory: {directory_size} bytes")
print(f"Dimensione della directory: {directory_size / (1024 ** 2):.2f} MB")

Esempio di output


Se la struttura delle directory è la seguente:

/path/to/your/directory
├── file1.txt (500 bytes)
├── subdir1
│   ├── file2.txt (1500 bytes)
│   └── file3.txt (3000 bytes)
└── subdir2
    └── file4.txt (2000 bytes)

Il risultato dell’esecuzione sarà il seguente:

Dimensione della directory: 7000 bytes  
Dimensione della directory: 0.01 MB

Spiegazione del codice

  • os.path.getsize(file_path): Ottiene la dimensione di un file in byte.
  • Gestione delle eccezioni: Gestisce il caso in cui un file venga rimosso o non sia accessibile durante l’elaborazione.
  • Conversione delle unità: Converte la dimensione del file da byte a una forma più leggibile (KB, MB, ecc.).

Esempio pratico: Calcolare la dimensione dei file con una specifica estensione


Per calcolare la dimensione totale dei file con una specifica estensione, è possibile modificare il codice come segue:

def calculate_size_by_extension(directory, extension):
    total_size = 0
    for dirpath, dirnames, filenames in os.walk(directory):
        for filename in filenames:
            if filename.endswith(extension):
                file_path = os.path.join(dirpath, filename)
                try:
                    total_size += os.path.getsize(file_path)
                except FileNotFoundError:
                    pass
    return total_size

# Esempio: Calcolare la dimensione totale dei file .txt
txt_size = calculate_size_by_extension(target_directory, ".txt")
print(f"Dimensione dei file .txt: {txt_size} bytes")

Praticità

  • Monitorare l’uso del disco su server o cloud storage.
  • Verificare il consumo di risorse di una cartella di progetto specifica.
  • Assistere nelle operazioni di pulizia quando lo spazio su disco è insufficiente.

Esempio pratico: Creazione di una copia di backup di tutti i file

Utilizzando os.walk, puoi esplorare ricorsivamente una directory e copiare tutti i file in una destinazione di backup. Questo ti permette di creare una copia di sicurezza dell’intera struttura delle directory.

Esempio di codice


Di seguito un esempio di codice che copia i file in una directory di backup:

import os
import shutil

# Specificare la directory di origine e quella di backup
source_directory = "/path/to/your/source_directory"
backup_directory = "/path/to/your/backup_directory"

def backup_files(source, backup):
    for dirpath, dirnames, filenames in os.walk(source):
        # Calcolare il percorso di backup
        relative_path = os.path.relpath(dirpath, source)
        backup_path = os.path.join(backup, relative_path)

        # Creare la directory di backup se non esiste
        os.makedirs(backup_path, exist_ok=True)

        for filename in filenames:
            source_file = os.path.join(dirpath, filename)
            backup_file = os.path.join(backup_path, filename)

            try:
                # Copiare il file
                shutil.copy2(source_file, backup_file)
                print(f"Copiato: {source_file} -> {backup_file}")
            except Exception as e:
                print(f"Impossibile copiare {source_file}: {e}")

# Eseguire il backup
backup_files(source_directory, backup_directory)

Esempio di output


Se la struttura delle directory è la seguente:

/path/to/your/source_directory
├── file1.txt
├── subdir1
│   ├── file2.txt
│   └── file3.txt
└── subdir2
    └── file4.txt

La directory di backup avrà la seguente struttura:

/path/to/your/backup_directory
├── file1.txt
├── subdir1
│   ├── file2.txt
│   └── file3.txt
└── subdir2
    └── file4.txt

Spiegazione del codice

  • os.makedirs(backup_path, exist_ok=True): Crea la directory di backup in modo ricorsivo. Se la directory esiste già, non genera errori grazie a exist_ok=True.
  • os.path.relpath(dirpath, source): Calcola il percorso relativo dalla directory di origine per creare la struttura di directory di backup.
  • shutil.copy2(source_file, backup_file): Copia i file, mantenendo anche i metadati come i timestamp.

Punti di attenzione

  1. Link simbolici: shutil.copy2 copia i link simbolici come normali file. Se desideri mantenere i link simbolici, dovrai applicare un trattamento speciale.
  2. Capacità del disco: Assicurati che ci sia abbastanza spazio sulla destinazione di backup.
  3. Permessi di accesso: Verifica di avere i permessi necessari per accedere ai file di origine.

Esempi avanzati

  • Effettuare il backup solo di file con una determinata estensione, aggiungendo una condizione come if filename.endswith(".txt").
  • Registrare un log del backup, scrivendo ogni file copiato in un file di log.
  • Eseguire un backup differenziale, copiando solo i file che sono stati modificati, confrontando i timestamp o i valori hash.

Conclusione

In questo articolo, abbiamo esplorato come utilizzare os.walk in Python per esplorare ricorsivamente una directory e le sue sottodirectory. Abbiamo anche visto vari esempi di utilizzo pratico, come la ricerca di file con estensioni specifiche, la gestione della profondità di esplorazione e l’ignoring di file nascosti. Inoltre, abbiamo esplorato come calcolare la dimensione di una directory e fare backup completi dei file.

Grazie alla flessibilità di os.walk, puoi automatizzare compiti che coinvolgono operazioni su directory e migliorare significativamente l’efficienza dei tuoi progetti. Approfitta di questa potente funzione per semplificare il lavoro con le directory nei tuoi progetti Python!

Indice