Un generator è una funzione o un'espressione che restituisce un tipo speciale di iteratore chiamato generator iterator.
I Generator-iterator sono pigri: non memorizzano i loro values in memoria, ma generano i loro valori quando servono.
Una funzione generatore è simile a qualsiasi altra funzione, ma contiene una o più espressioni yield.
Ogni yield sospende l'esecuzione del codice, salvando lo stato di esecuzione corrente (comprese tutte le variabili locali e le istruzioni try).
Quando il generatore riprende, riparte dallo stato in cui era stato sospeso, a differenza delle funzioni normali che si azzerano a ogni chiamata.
I generatori si costruiscono più o meno come le altre funzioni iterative o ricorsive, ma richiedono un'espressione yield, che esploreremo più a fondo tra poco.
Un esempio è una funzione che restituisce i quadrati a partire da un array di numeri. Così com'è scritta, la funzione deve elaborare tutto l'input prima di poter restituire dei valori:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
Puoi convertire quella funzione in un generatore così:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
Il motivo è che usi un generatore quando non hai bisogno di produrre tutti i valori in una sola volta. Così risparmi memoria e potenza di calcolo, perché viene calcolato solo il valore su cui stai lavorando in quel momento.
I generatori possono essere usati al posto della maggior parte degli iterables in Python.
Questo include funzioni o oggetti che richiedono un iterable/iterator come argomento.
Per usare il generatore squares_generator():
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
I valori all'interno di un generator possono anche essere prodotti o recuperati tramite la funzione next().
next() chiama il metodo __next__() di un oggetto generator-iterator, «avanzando» o valutando il codice fino alla sua espressione yield, che a quel punto «produce» o restituisce un valore:
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
Quando un generator-iterator è stato consumato del tutto e non ha più valori da restituire, solleva un errore StopIteration.
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
I generator-iterator sono un sottoinsieme speciale degli iteratori.
Gli Iterators sono il meccanismo o protocollo che permette di iterare su iterables.
I generator-iterator e gli iteratori restituiti dai comuni iterables di Python si comportano in modo molto simile, ma ci sono alcune differenze importanti da tenere presente:
indexes.
Non puoi fare riferimento a un valore precedente o futuro usando addizioni o sottrazioni e non puoi usare la notazione con parentesi quadre ([]) né lo slicing.len(), perché non hanno una lunghezza.generator-iterator infinito!L'espressione yield è molto simile all'espressione return.
A differenza dell'espressione return, yield cede i valori al chiamante in un punto preciso, sospendendo la valutazione o la restituzione di eventuali altri valori finché non vengono richiesti.
Quando yield viene valutato, mette in pausa l'esecuzione della funzione che lo contiene e restituisce i valori della funzione in quel preciso momento.
La funzione poi rimane nello scope, e quando viene chiamato __next__(), l'esecuzione riprende finché non si incontra di nuovo yield.
L'uso di espressioni yield è vietato al di fuori delle funzioni.
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
I generatori sono utili in molte applicazioni.
Quando lavori con una raccolta di valori potenzialmente grande, potresti non volerli mettere tutti in memoria. Un generatore può essere usato per lavorare su dati più grandi un pezzo alla volta, risparmiando memoria e migliorando le prestazioni.
I generatori sono anche molto utili quando un processo o un calcolo è complesso, costoso o infinito:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
Ora, ogni volta che viene chiamato __next__() sull'oggetto infinite_sequence, restituirà il numero precedente + 1.
Conda Airlines è la più grande compagnia aerea del mondo della programmazione, con oltre 10.000 voli al giorno!
Al momento assegna tutti i posti ai passeggeri a mano: questa operazione dovrà essere automatizzata.
Ha chiesto a te di creare un software per automatizzare l'assegnazione dei posti ai passeggeri. Il software deve essere efficiente in termini di memoria e performante.
Conda vuole generare le lettere dei posti per i suoi aerei.
Un aereo è composto da file di posti.
Ogni fila ha 4 posti.
I posti di ogni fila si chiamano sempre A, B, C e D.
Il primo posto della fila è A, il secondo posto della fila è B, e così via.
Dopo aver raggiunto D, si ricomincia da A.
Implementa una funzione generate_seat_letters(<number>) che accetta un int che indica quante lettere dei posti generare.
La funzione deve quindi restituire un iterabile di lettere dei posti.
>>> letters = generate_seat_letters(4)
>>> next(letters)
"A"
>>> next(letters)
"B"
Conda vuole un sistema in grado di generare un dato numero di posti per i suoi aerei.
Ogni aereo ha 4 posti in ogni fila.
Le file sono definite da numeri, a partire da 1 e via via crescenti.
I posti devono essere ordinati, così: 1A, 1B, 1C, 1D, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, ...
Ecco un esempio:
| x | 1 | 2 |
|---|---|---|
| Fila | 5 | 21 |
| Lettera del posto | A | D |
| Risultato | 5A | 21D |
Molte compagnie aeree non hanno la fila numero 13 sui loro voli, a causa della superstizione dei passeggeri. Anche Conda Airlines segue questa convenzione, quindi assicurati di non generare posti per la fila numero 13.
Implementa una funzione generate_seats(<number>) che accetta un int che indica quanti posti generare.
La funzione deve quindi restituire un iterabile di posti generati.
>>> seats = generate_seats(10)
>>> next(seats)
"1A"
>>> next(seats)
"1B"
Ora che hai una funzione che genera i posti, puoi usarla per assegnare i posti ai passeggeri.
Implementa una funzione assign_seats(<passengers>) che accetta una list di nomi di passeggeri.
La funzione deve quindi restituire un dizionario con passenger come chiave e seat_number come valore.
>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob']
>>> assign_seats(passengers)
{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}
Conda Airlines vorrebbe un codice unico per ogni biglietto. Dato che è una compagnia aerea grande, ha molti voli. Questo significa che ci sono più voli con lo stesso numero di posto. Vogliono che tu crei un sistema che formuli un biglietto unico, una stringa lunga 12 caratteri.
Questo codice inizia con l'assigned_seat seguito dal flight_id.
Il resto del codice è riempito con 0s.
Implementa una funzione generate_codes(<seat_numbers>, <flight_id>) che accetta una list di seat_numbers e una string con il numero del volo.
La funzione deve quindi restituire un generator che produce un ticket_number.
>>> seat_numbers = ['1A', '17D']
>>> flight_id = 'CO1234'
>>> ticket_ids = generate_codes(seat_numbers, flight_id)
>>> next(ticket_ids)
'1ACO12340000'
>>> next(ticket_ids)
'17DCO1234000'
Iscriviti a Exercism per imparare e padroneggiare Python con 17 concetti146 esercizi e il mentoring di persone reali, tutto gratis.