Storia della questione
Python, a partire dalla versione 2.4, ha arricchito le comprensioni di lista con le cosiddette "espressioni generatore". Queste permettono di creare una sequenza di valori pigri, simile ai generatori, ma in una forma compatta e leggibile.
Problema
Le comprensioni di lista ([x for x in iterable]) creano una lista, caricando subito tutti gli elementi in memoria. Questo è inefficiente o addirittura pericoloso se il numero di elementi è molto grande. Le funzioni generatore (usando yield) sono più flessibili, ma richiedono una definizione di funzione separata e un numero maggiore di righe di codice.
Soluzione
Le espressioni generatore ((x for x in iterable)) forniscono una sintassi concisa per generare sequenze pigre (gli elementi vengono calcolati al bisogno, non caricati tutti insieme). Hanno un aspetto simile alle comprensioni di lista, ma usano le parentesi tonde:
# Comprensione di lista carica tutto in memoria squares_list = [x**2 for x in range(10**6)] # Espressione generatore: gli elementi arrivano su richiesta, quasi non si utilizza memoria squares_gen = (x**2 for x in range(10**6)) # Ottenere i primi cinque valori del generatore for _ in range(5): print(next(squares_gen))
Caratteristiche chiave:
È possibile "riattraversare" la stessa espressione generatore più volte?
No, dopo aver iterato una volta il generatore "si esaurisce". Per riattraversare è necessario creare un nuovo generatore o utilizzare una comprensione di lista.
it = (x for x in range(3)) print(list(it)) # [0,1,2] print(list(it)) # [] — non è più possibile ottenere valori
I generatori mantengono lo stato tra gli utilizzi?
Sì, l'espressione generatore mantiene la "posizione" tra le chiamate a next() (o durante ogni iterazione), ma non può essere ripristinata all'inizio a meno che non si crei un nuovo oggetto.
È possibile utilizzare un'espressione generatore più volte nella stessa riga?
No! Se "disimballi" un generatore in più luoghi contemporaneamente (ad esempio, in più funzioni senza restituirlo in una lista), parte dei dati andrà persa — ogni uso secondario sposta il puntatore in avanti.
g = (x for x in range(3)) print(sum(g), list(g)) # sum(g) otterrà tutto, list(g) rimarrà vuoto
Nel progetto per l'analisi di grandi file è stato utilizzato:
data = (parse_line(line) for line in file) process(list(data)) other_process(list(data))
Pro:
Contro:
Utilizzato la comprensione di lista, se era necessario un riutilizzo dei dati, o creato un generatore per un consumo una tantum:
# Generatore solo per analisi una tantum (ad esempio, contare la somma) total = sum(parse_line(line) for line in file)
Pro:
Contro: