Python: ciclo while

Con il ciclo while di Python, un blocco di codice viene eseguito ripetutamente finché una condizione è vera. I cicli while sono utilizzati principalmente in Python quando il numero di iterazioni necessarie non è noto in anticipo. Vi spieghiamo come funziona il ciclo while in Python.

Consiglio

Imparate a scrivere da soli il codice Python con il nostro tutorial su Python.

Cos’è il ciclo while in Python?

Il ciclo while in Python è una struttura di controllo. Le strutture di controllo determinano il percorso del codice da seguire in fase di esecuzione. I cicli vengono generalmente utilizzati per eseguire ripetutamente un blocco di codice. Di seguito vi presentiamo le strutture di controllo più importanti in Python:

Struttura di controllo in Python Spiegazione
Ramificazione if-else Esegue il blocco di codice una volta se la condizione è vera.
Ciclo while in Python Esegue ripetutamente il blocco di codice finché la condizione è vera.
Ciclo for in Python Itera gli elementi di un insieme, eseguendo un blocco di codice per ogni elemento.

In genere, i programmatori e le programmatrici alle prime armi che non conoscono i cicli cercano di emularne le funzionalità. Invece di definire una struttura di dati con diversi elementi e di elaborarla con un ciclo, determinate una variabile specifica per ogni set di dati. Inoltre, il codice per l’elaborazione dei set di dati viene duplicato. Il risultato è un codice subottimale, mostrato qui con l’esempio di tre persone di cui vengono indicati anche il nome e l’età:

person1 = ('Jim', 17)
person2 = ('Jack', 42)
person3 = ('John', 63)
print(f"{person1[0]}, {person1[1]} years old")
print(f"{person2[0]}, {person2[1]} years old")
print(f"{person3[0]}, {person3[1]} years old")

Oltre all’indesiderata duplicazione del codice, questo approccio pone un problema logistico: se il numero del set di dati non è noto fino all’esecuzione, non è possibile definire in anticipo le singole variabili corrispondenti. Per risolvere questo problema, si utilizzano insiemi di elementi e cicli per elaborarli.

Se in fase di esecuzione si sa quante ripetizioni sono necessarie, il ciclo for in Python dà il risultato migliore. Come esempio, mappiamo il codice per ottenere come risultato tre persone, inclusa l’indicazione della loro età, utilizzando un ciclo for. Il codice funziona senza duplicazioni e contiene solo due variabili, indipendentemente dal numero del set di dati:

 

people = ('Jim', 'Jack', 'John')
ages = (17, 42, 63)
for person, age in zip(people, ages):
    print(f"{person}, {age} years old")

A differenza del ciclo for, il ciclo while viene utilizzato in Python quando non si sa quante ripetizioni sono necessarie. Un esempio è lo scambio di messaggi attraverso una connessione aperta. Finché la connessione è in atto, i messaggi vengono elaborati. Un’istruzione if nel corpo del ciclo valuta un segnale e termina la connessione, se lo si desidera:

while connection_open():
    print('Ready to receive')
    process_messages()
    if should_close_connection():
        close_connection()
# once loop terminates
print('Connection was closed')

Inoltre, i cicli while vengono utilizzati per ripetizioni praticamente illimitate. Esempi noti sono i bancomat, la riga di comando di Linux e il “Read-Eval-Print Loop” (REPL) di Python. Di seguito trovate una rappresentazione schematica di un’implementazione REPL tramite un ciclo infinito while:

# Loop
while True:
    # Read user input
    user_input = read_input()
    # Evaluate input and produce result
    result = eval(user_input)
    # Print result
    print(result)

Come funziona il ciclo while in Python?

Il ciclo while di Python funziona in modo simile alla ramificazione if-else in Python. Entrambe le strutture di controllo sono composte da due parti:

  1. Una condizione che viene valutata
  2. Un corpo con dichiarazioni

La differenza sta nella frequenza con cui il corpo viene eseguito. Il corpo di un’istruzione if viene eseguito al massimo una volta:

if condition:
    run_once()

A differenza dell’istruzione if, il corpo del ciclo while in Python viene eseguito più volte, se necessario:

while condition:
    run_again()

Ci atteniamo allo schema generale durante l’esecuzione di un ciclo while in Python:

  1. La condizione viene valutata.
  2. Se la condizione è vera, il corpo del ciclo viene eseguito.
  3. La condizione viene nuovamente valutata:
    1. Se la condizione è ancora vera, il processo si ripete.
    2. Se la condizione non è vera, il ciclo termina.

In maniera analoga all’istruzione if, un ciclo while in Python può avere un blocco else opzionale. Il blocco else viene eseguito una volta se la condizione è o diventa falsa:

while False:
    # this code doesn't loop
    never_runs()
else:
    # instead, this code runs once
    runs_once()

Qual è la differenza tra i cicli for e while in Python?

Il ciclo while è anche correlato al ciclo for di Python. Entrambi eseguono ripetutamente un blocco di codice. Si parla anche di “iterazioni” o di “iterare”. La differenza consiste nel numero di ripetizioni.

In Python, i cicli for sono utilizzati principalmente per iterare gli elementi di un insieme. In questo caso, il numero massimo di iterazioni è limitato dalla lunghezza dell’insieme. Iteriamo le lettere della parola “Python” ed emettiamo ogni lettera singolarmente:

for letter in 'Python':
    print(letter)

Vediamo alcuni esempi. Ricreiamo la funzionalità di un ciclo for convenzionale con una variabile numerica come un ciclo while di Python. A tale scopo, definiamo una variabile contatore al di fuori del ciclo e ne incrementiamo il valore all’interno del corpo del ciclo:

counter = 0
limit = 10
while counter < limit:
    print(counter)
    counter += 1

Il ciclo for equivalente è più breve e diretto:

for counter in range(10):
    print(counter)

Funziona in modo simile se utilizziamo un ciclo while per iterare le lettere di una parola. Usiamo un iteratore e la funzione next(). Se l’iteratore è esaurito, al posto di una lettera viene restituito None; quindi, il ciclo termina. Il codice risultante è molto più complicato dell’equivalente ciclo for. Evidentemente, il ciclo while di Python non è lo strumento ottimale per risolvere questo problema:

word = 'Python'
letters = iter(word)
letter = ''
while letter is not None:
    letter = next(letters, None)
    if letter:
        print(letter)

Attenzione ai cicli infiniti while in Python

I cicli while in Python sono particolarmente interessanti per implementare cicli infiniti. All’inizio può sembrare assurdo. Dopo tutto, i cicli infiniti prodotti accidentalmente sono temuti. La condizione non diventa mai falsa; quindi, il programma si blocca:

while True:
    print("Forever…")

In effetti, ci sono molti casi d’uso per cicli infiniti intenzionali. Un ciclo infinito while prodotto accidentalmente è solitamente causato da un’espressione che valuta sempre True. Di seguito un esempio:

while 1 == 1 + 1 - 1:
    print("And ever…")
Consiglio

Se vi trovate intrappolati in un ciclo infinito while nel REPL di Python, la combinazione di tasti Ctrl + C vi aiuterà. In questo modo verrà inviato un segnale di stop all’interprete Python, che interrompe l’esecuzione del ciclo.

Interrompere e saltare le esecuzioni di un ciclo while in Python

In generale, un ciclo while itera fino a quando la condizione del ciclo non diventa falsa. Un trucco comune è quello di utilizzare una variabile “flag” come condizione. A tale scopo, una variabile booleana viene definita al di fuori del ciclo e valutata nella condizione all’interno dello stesso. Quando viene raggiunta una determinata condizione all’interno del corpo, si attiva il flag. Quando la condizione viene valutata prima dell’esecuzione successiva, il nuovo valore fa terminare il ciclo:

aborted = False
while not aborted:
    print("Still going…")
    if some_cond:
        # will prevent next iteration
        aborted = True

Questo schema si trova spesso, ma non è particolarmente elegante. Cosa succede se nel corpo del ciclo segue altro codice dopo che la variabile flag è stata impostata? Dovrebbe essere saltato. In pratica, Python conosce l’istruzione break nei cicli while.

Se viene eseguita un’istruzione break all’interno di un ciclo, questo termina immediatamente. Ciò rende l’istruzione break nei cicli simile a quella return nelle funzioni. Tuttavia, break non restituisce alcun valore. È comune utilizzare l’istruzione break per terminare un ciclo infinito:

while True:
    print("Still going…")
    if some_cond:
        break
        # we never get here
        print("We shouldn't be here")
# we end up here after breaking
print("Done.")

Concettualmente correlata all’istruzione break è l’istruzione continue. Se nel corpo del ciclo viene eseguita un’istruzione continue, il codice seguente viene saltato. Si prosegue con l’iterazione successiva. Con break e continue è possibile implementare semplici menu basati su testo, come quelli conosciuti nei primi giochi per computer:

# `continue` takes us here
while True:
    print("Press G to start a new game")
    print("Press S to see stats")
    print("Press M for main menu")
    print("Press Q to quit")
    
    key_press = input("Your choice \n")[0].upper()
    print(f"You pressed {key_press}")
    
    if key_press == "G":
        # start game routines
        print("Starting game …")
    elif key_press == "S":
        # show stats
        print("Showing stats …")
    elif key_press == "M":
        # back to main menu
        print("Returning to menu")
        continue
    elif key_press == "Q":
        # break out of loop
        print("Quitting")
        break
    else:
        print("Unknown command. Try again")
# `break` takes us here
...

Uscire dai cicli while annidati in Python

L’uso di cicli annidati genera rapidamente un po’ di confusione. A questo punto è utile uscire dall’ultimo ciclo avviato con un’istruzione break. Ma cosa succede se volessimo uscire nello stesso momento anche da un ciclo superiore? Non esiste una parola chiave apposita per questo caso; in linea di principio, una soluzione che utilizzi una variabile flag funziona in questo contesto.

Un modo più elegante per uscire dai cicli while annidati in Python è quello di combinare abilmente break, continue ed else. Costruiamo il nostro costrutto di ciclo in modo che quello esterno venga terminato quando viene eseguita un’istruzione break nel ciclo interno. A tale scopo, utilizziamo un’istruzione continue all’interno del blocco else interno per saltare l’istruzione break esterna, se necessario:

# `continue` takes us here
while outer_cond:
    while inner_cond:
        ...
        if some_cond:
            print("Breaking out of inner loop")
            break
    # no inner `break` occured
    else:
        print("Continuing outer loop")
        # skip rest of outer loop body
        continue
    # we only get here if inner `break` occured
    print("Breaking out of outer loop")
    break
# outer `break` takes us here
...

Come si può utilizzare il ciclo while in Python?

In pratica, ci sono molti casi d’uso diversi per il ciclo while in Python. In genere, i cicli while vengono utilizzati per algoritmi in cui il numero di ripetizioni non è fissato a priori o cambia durante l’esecuzione. Questi cicli sono spesso utilizzati in combinazione con altre strutture di controllo, come le ramificazioni e le istruzioni try-else. Vediamo alcuni esempi.

Consumare un insieme in Python con un ciclo while

Per iterare gli elementi di un insieme, in Python si adatta bene il ciclo for. Questo almeno finché non si modifica l’insieme all’interno del corpo del ciclo. Ma cosa succede se apportiamo delle modifiche durante l’iterazione degli elementi? Immaginiamo di voler rimuovere elementi dall’insieme durante l’iterazione. In questo caso, si dice che l’insieme è “consumato”.

I cicli for possono causare strani errori se l’insieme sottostante cambia durante l’iterazione. Se vogliamo consumare un insieme, è adatto il ciclo while in Python. Utilizziamo l’insieme direttamente come condizione del ciclo while. Finché l’insieme contiene elementi, viene valutato come vero nel contesto booleano. Se l’insieme è vuoto, il ciclo termina:

pieces = ['x', 'o', 'o', 'o', 'x', 'o', 'x', 'x']
while pieces:
    piece = pieces.pop()
    print(f"Removed {piece}")
# test
assert pieces == []

Implementare la propria funzione range() con il ciclo while in Python

I cicli while in Python possono essere utilizzati per implementare i cosiddetti generatori. Un generatore è una funzione che utilizza l’istruzione yield e genera valori su richiesta. Scriviamo la nostra implementazione della funzione range(). Utilizziamo l’istruzione yield all’interno di un ciclo while per generare numeri progressivi. Quando si raggiunge l’istruzione yield, viene emesso un valore e il ciclo viene messo in pausa:

def my_range(start, stop):
    # only positive ranges implemented
    if stop <= start:
        return None
    current = start
    while current < stop:
        yield current
        # next call of next() continues here
        current += 1
# test
assert list(my_range(7, 9)) == list(range(7, 9))

Ottimizzazione di un modello con il ciclo while di Python

L’ottimizzazione dei modelli fa parte del repertorio standard delle discipline scientifiche. Un modello viene calcolato in base a un insieme di parametri. I parametri vengono quindi regolati e il modello viene calcolato nuovamente. Una funzione obiettivo viene utilizzata per valutare se la modifica dei parametri ha portato a un modello migliore. Se è così, il processo viene ripetuto. In questo modo è possibile trovare iterativamente i parametri ottimali per il modello.

Normalmente, il modello si assesta dopo alcune ripetizioni, per cui l’avanzamento diventa sempre più piccolo. Se si scende al di sotto di una certa soglia, si interrompe l’ottimizzazione. Per assicurarci che il ciclo termini, limitiamo anche il numero massimo di esecuzioni. Di seguito vi presentiamo un approccio schematico all’ottimizzazione del modello utilizzando un ciclo while in Python:

limit = 5
round = 0
progress = True
while progress and round < limit:
    # attempt next optimization
    round += 1
    # compute optimized parameters
    params = optimize(params)
    # make a copy of the old model
    old_model = model
    # compute new model using optimized parameters
    model = run(model, params)
    # worthwhile to further optimize?
    progress = has_improved(model, old_model)

Implementare una connessione in Python con il ciclo while e try-except

Un tentativo di connessione può non andare a buon fine. Pertanto, è auspicabile che la connessione venga stabilita con diversi tentativi. Poiché non possiamo sapere in anticipo quanti tentativi saranno necessari, utilizziamo un ciclo while in Python. Inoltre, limitiamo il numero massimo di tentativi. Se nessuno dei tentativi è andato a buon fine, interrompiamo il processo con un messaggio di errore.

Un approccio schematico alla soluzione si presenterebbe così: utilizziamo un’istruzione try-except per rilevare un errore durante la creazione di una connessione. L’uso di un’istruzione break nel blocco try e di un’istruzione continue nel blocco except garantisce un’iterazione corretta. Se la connessione fallisce, riproviamo con continue. Se la connessione è stata stabilita, chiudiamo il ciclo con break:

max_tries = 10
attempt = 0
conn = None
# `continue` takes us here
while attempt < max_tries:
    attempt += 1
    print("Trying to get a connection")
    try:
        # might raise `ConnectionException`
        conn = get_connection()
        # got our connection
        break
    # `get_connection()` raised `ConnectionException`
    except ConnectionException:
        print("Something went wrong. Trying again")
        continue
# went through `max_tries` unsuccessful connection attempts
else:
    assert conn is None
    print("Unable to connect")
# `break` takes us here
assert conn is not None
print("Connection established")

Iterazione di strutture ricorsive con il ciclo while di Python

Il ciclo while di Python è adatto a risolvere problemi ricorsivi. Ad esempio, il ciclo è adatto per l’iterazione di:

  • Elenchi annidati
  • Strutture ad albero
  • Grafici

Vediamo come funziona con l’esempio di una matrioska. Il noto giocattolo è costituito da bambole annidate. Il numero di livelli non è visibile dall’esterno. Procediamo invece in modo iterativo: apriamo la bambola più esterna e vediamo cosa troviamo all’interno. Se si tratta di un’altra matrioska, ripetiamo il processo; questo è un caso tipico di utilizzo di un ciclo while.

Modelliamo la matrioska come un elenco annidato con un singolo elemento ciascuno. Si può includere un’altra lista o un oggetto che non è una lista. Iteriamo la matrioska finché si tratta di una lista. All’interno del corpo del ciclo, utilizziamo un’assegnazione per scendere di un livello. A un certo punto troviamo un elemento che non è una lista. A questo punto abbiamo trovato l’oggetto contenuto e interrompiamo l’iterazione:

def open_matroshka(matroshka):
    """
    * Matroshka dolls stacked five levels deep, with `None` inside:
    `matroshka = [[[[[None]]]]]`
    """
    while type(matroshka) is list:
        print("Opening the next matroshka")
        # go one level deeper
        matroshka = matroshka.pop()
    else:
        print(f"Reached the bottom and found {matroshka}")
        return matroshka
# test
matroshka = [[[[[None]]]]]
assert open_matroshka(matroshka) is None

Questo semplice approccio funziona indipendentemente dal livello raggiunto delle bambole annidate. Il nostro algoritmo scava fino in fondo e fa emergere l’oggetto contenuto. È questa la magia del ciclo while di Python.

Esecuzione di un ciclo while in Python su una scacchiera

Uno scenario d’uso comune dei cicli while in Python è quello di spostare un pezzo su una scacchiera. Se si vuole avere la garanzia di visitare tutti i quadrati, sono necessari due cicli annidati. In questo caso, è necessario tenere conto del tempo di esecuzione: con aree di grandi dimensioni, il programma può durare a lungo.

Implementiamo una semplice “passeggiata casuale”, in cui un pezzo sulla scacchiera viene spostato in modo casuale fino a raggiungere una destinazione. Un simile schema di movimento può essere riscontrato, ad esempio, nel movimento di una particella in un liquido o di una mosca che vola nello spazio. Poiché non è noto a priori il numero di iterazioni necessarie, utilizziamo un ciclo while in Python.

Definiamo innanzitutto la funzione random_walk() che contiene il ciclo while. Con l’operatore di Python per “is-uguale” controlliamo se la posizione corrente è l’obiettivo. In caso contrario, si procede a un’ulteriore iterazione:

def random_walk(board = (10, 10), goal = (4, 4), start = (0, 0)):
    # ensure arguments are valid
    if not (goal[0] in range(board[0]) and goal[1] in range(board[1]) and start[0] in range(board[0]) and start[1] in range(board[1])):
        print(f"Goal {goal} and / or start position {start} outside of board with dimensions {board}")
        return None, 0
    steps = 0
    pos = start
    # as long as we haven't reached the goal
    while not pos == goal:
        # move to neighboring position
        pos = get_neighbor(pos, board)
        steps += 1
        print(f"Moved to position {pos}")
    print(f"Reached goal at {pos} after {steps} steps")
    return pos, steps

Inoltre, definiamo una funzione ausiliaria get_neighbor() che restituisce un possibile campo intorno a una certa posizione:

def get_neighbor(pos, bounds):
    from random import choice
    """
        x = 0    . . .    m
            - - - - - - - -
     y = 0 |
           |
         . |           (x, y-1)
         . |  (x-1, y) (x, y)  (x+1, y)
         . |           (x, y+1)
           |
         n |
   
    """
    x, y = pos
    # computer neighbors
    neighbors = [ (x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1) ]
    # filter out neighbors that are outside of bounds
    neighbors = [ pos for pos in neighbors if 0 <= pos[0] < bounds[0] and 0 <= pos[1] < bounds[1] ]
    # select a random neighbor
    neighbor = choice(neighbors)
    return neighbor

Testiamo quindi l’implementazione della nostra passeggiata casuale:

random_walk(board = (10, 10), goal = (4, 4), start = (5, 7))