Con il ciclo while di Python, un blocco di codice viene eseguito ri­pe­tu­ta­men­te finché una con­di­zio­ne è vera. I cicli while sono uti­liz­za­ti prin­ci­pal­men­te in Python quando il numero di ite­ra­zio­ni ne­ces­sa­rie non è noto in anticipo. Vi spie­ghia­mo 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 de­ter­mi­na­no il percorso del codice da seguire in fase di ese­cu­zio­ne. I cicli vengono ge­ne­ral­men­te uti­liz­za­ti per eseguire ri­pe­tu­ta­men­te un blocco di codice. Di seguito vi pre­sen­tia­mo le strutture di controllo più im­por­tan­ti in Python:

Struttura di controllo in Python Spie­ga­zio­ne
Ra­mi­fi­ca­zio­ne if-else Esegue il blocco di codice una volta se la con­di­zio­ne è vera.
Ciclo while in Python Esegue ri­pe­tu­ta­men­te il blocco di codice finché la con­di­zio­ne è vera.
Ciclo for in Python Itera gli elementi di un insieme, eseguendo un blocco di codice per ogni elemento.

In genere, i pro­gram­ma­to­ri e le pro­gram­ma­tri­ci alle prime armi che non conoscono i cicli cercano di emularne le fun­zio­na­li­tà. Invece di definire una struttura di dati con diversi elementi e di ela­bo­rar­la con un ciclo, de­ter­mi­na­te una variabile specifica per ogni set di dati. Inoltre, il codice per l’ela­bo­ra­zio­ne dei set di dati viene duplicato. Il risultato è un codice su­bot­ti­ma­le, 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’in­de­si­de­ra­ta du­pli­ca­zio­ne del codice, questo approccio pone un problema logistico: se il numero del set di dati non è noto fino all’ese­cu­zio­ne, non è possibile definire in anticipo le singole variabili cor­ri­spon­den­ti. Per risolvere questo problema, si uti­liz­za­no insiemi di elementi e cicli per ela­bo­rar­li.

Se in fase di ese­cu­zio­ne si sa quante ri­pe­ti­zio­ni sono ne­ces­sa­rie, il ciclo for in Python dà il risultato migliore. Come esempio, mappiamo il codice per ottenere come risultato tre persone, inclusa l’in­di­ca­zio­ne della loro età, uti­liz­zan­do un ciclo for. Il codice funziona senza du­pli­ca­zio­ni e contiene solo due variabili, in­di­pen­den­te­men­te 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 dif­fe­ren­za del ciclo for, il ciclo while viene uti­liz­za­to in Python quando non si sa quante ri­pe­ti­zio­ni sono ne­ces­sa­rie. Un esempio è lo scambio di messaggi at­tra­ver­so una con­nes­sio­ne aperta. Finché la con­nes­sio­ne è in atto, i messaggi vengono elaborati. Un’istru­zio­ne if nel corpo del ciclo valuta un segnale e termina la con­nes­sio­ne, 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 uti­liz­za­ti per ri­pe­ti­zio­ni pra­ti­ca­men­te il­li­mi­ta­te. Esempi noti sono i bancomat, la riga di comando di Linux e il “Read-Eval-Print Loop” (REPL) di Python. Di seguito trovate una rap­pre­sen­ta­zio­ne sche­ma­ti­ca di un’im­ple­men­ta­zio­ne 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 ra­mi­fi­ca­zio­ne if-else in Python. Entrambe le strutture di controllo sono composte da due parti:

  1. Una con­di­zio­ne che viene valutata
  2. Un corpo con di­chia­ra­zio­ni

La dif­fe­ren­za sta nella frequenza con cui il corpo viene eseguito. Il corpo di un’istru­zio­ne if viene eseguito al massimo una volta:

if condition:
    run_once()

A dif­fe­ren­za dell’istru­zio­ne if, il corpo del ciclo while in Python viene eseguito più volte, se ne­ces­sa­rio:

while condition:
    run_again()

Ci atteniamo allo schema generale durante l’ese­cu­zio­ne di un ciclo while in Python:

  1. La con­di­zio­ne viene valutata.
  2. Se la con­di­zio­ne è vera, il corpo del ciclo viene eseguito.
  3. La con­di­zio­ne viene nuo­va­men­te valutata:
    1. Se la con­di­zio­ne è ancora vera, il processo si ripete.
    2. Se la con­di­zio­ne non è vera, il ciclo termina.

In maniera analoga all’istru­zio­ne if, un ciclo while in Python può avere un blocco else opzionale. Il blocco else viene eseguito una volta se la con­di­zio­ne è o diventa falsa:

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

Qual è la dif­fe­ren­za tra i cicli for e while in Python?

Il ciclo while è anche correlato al ciclo for di Python. Entrambi eseguono ri­pe­tu­ta­men­te un blocco di codice. Si parla anche di “ite­ra­zio­ni” o di “iterare”. La dif­fe­ren­za consiste nel numero di ri­pe­ti­zio­ni.

In Python, i cicli for sono uti­liz­za­ti prin­ci­pal­men­te per iterare gli elementi di un insieme. In questo caso, il numero massimo di ite­ra­zio­ni è limitato dalla lunghezza dell’insieme. Iteriamo le lettere della parola “Python” ed emettiamo ogni lettera sin­go­lar­men­te:

for letter in 'Python':
    print(letter)

Vediamo alcuni esempi. Ricreiamo la fun­zio­na­li­tà di un ciclo for con­ven­zio­na­le con una variabile numerica come un ciclo while di Python. A tale scopo, definiamo una variabile contatore al di fuori del ciclo e ne in­cre­men­tia­mo il valore all’interno del corpo del ciclo:

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

Il ciclo for equi­va­len­te è più breve e diretto:

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

Funziona in modo simile se uti­liz­zia­mo 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 re­sti­tui­to None; quindi, il ciclo termina. Il codice ri­sul­tan­te è molto più com­pli­ca­to dell’equi­va­len­te ciclo for. Evi­den­te­men­te, 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)

At­ten­zio­ne ai cicli infiniti while in Python

I cicli while in Python sono par­ti­co­lar­men­te in­te­res­san­ti per im­ple­men­ta­re cicli infiniti. All’inizio può sembrare assurdo. Dopo tutto, i cicli infiniti prodotti ac­ci­den­tal­men­te sono temuti. La con­di­zio­ne non diventa mai falsa; quindi, il programma si blocca:

while True:
    print("Forever…")

In effetti, ci sono molti casi d’uso per cicli infiniti in­ten­zio­na­li. Un ciclo infinito while prodotto ac­ci­den­tal­men­te è so­li­ta­men­te causato da un’espres­sio­ne che valuta sempre True. Di seguito un esempio:

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

Se vi trovate in­trap­po­la­ti in un ciclo infinito while nel REPL di Python, la com­bi­na­zio­ne di tasti Ctrl + C vi aiuterà. In questo modo verrà inviato un segnale di stop all’in­ter­pre­te Python, che in­ter­rom­pe l’ese­cu­zio­ne del ciclo.

In­ter­rom­pe­re e saltare le ese­cu­zio­ni di un ciclo while in Python

In generale, un ciclo while itera fino a quando la con­di­zio­ne del ciclo non diventa falsa. Un trucco comune è quello di uti­liz­za­re una variabile “flag” come con­di­zio­ne. A tale scopo, una variabile booleana viene definita al di fuori del ciclo e valutata nella con­di­zio­ne all’interno dello stesso. Quando viene raggiunta una de­ter­mi­na­ta con­di­zio­ne all’interno del corpo, si attiva il flag. Quando la con­di­zio­ne viene valutata prima dell’ese­cu­zio­ne suc­ces­si­va, 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 è par­ti­co­lar­men­te 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’istru­zio­ne break nei cicli while.

Se viene eseguita un’istru­zio­ne break all’interno di un ciclo, questo termina im­me­dia­ta­men­te. Ciò rende l’istru­zio­ne break nei cicli simile a quella return nelle funzioni. Tuttavia, break non re­sti­tui­sce alcun valore. È comune uti­liz­za­re l’istru­zio­ne 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.")

Con­cet­tual­men­te correlata all’istru­zio­ne break è l’istru­zio­ne continue. Se nel corpo del ciclo viene eseguita un’istru­zio­ne continue, il codice seguente viene saltato. Si prosegue con l’ite­ra­zio­ne suc­ces­si­va. Con break e continue è possibile im­ple­men­ta­re semplici menu basati su testo, come quelli co­no­sciu­ti 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 ra­pi­da­men­te un po’ di con­fu­sio­ne. A questo punto è utile uscire dall’ultimo ciclo avviato con un’istru­zio­ne 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. Co­struia­mo il nostro costrutto di ciclo in modo che quello esterno venga terminato quando viene eseguita un’istru­zio­ne break nel ciclo interno. A tale scopo, uti­liz­zia­mo un’istru­zio­ne continue all’interno del blocco else interno per saltare l’istru­zio­ne break esterna, se ne­ces­sa­rio:

# `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ò uti­liz­za­re 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 uti­liz­za­ti per algoritmi in cui il numero di ri­pe­ti­zio­ni non è fissato a priori o cambia durante l’ese­cu­zio­ne. Questi cicli sono spesso uti­liz­za­ti in com­bi­na­zio­ne con altre strutture di controllo, come le ra­mi­fi­ca­zio­ni e le istru­zio­ni 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 ap­por­tia­mo delle modifiche durante l’ite­ra­zio­ne degli elementi? Im­ma­gi­nia­mo di voler rimuovere elementi dall’insieme durante l’ite­ra­zio­ne. In questo caso, si dice che l’insieme è “consumato”.

I cicli for possono causare strani errori se l’insieme sot­to­stan­te cambia durante l’ite­ra­zio­ne. Se vogliamo consumare un insieme, è adatto il ciclo while in Python. Uti­liz­zia­mo l’insieme di­ret­ta­men­te come con­di­zio­ne 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 == []

Im­ple­men­ta­re la propria funzione range() con il ciclo while in Python

I cicli while in Python possono essere uti­liz­za­ti per im­ple­men­ta­re i co­sid­det­ti ge­ne­ra­to­ri. Un ge­ne­ra­to­re è una funzione che utilizza l’istru­zio­ne yield e genera valori su richiesta. Scriviamo la nostra im­ple­men­ta­zio­ne della funzione range(). Uti­liz­zia­mo l’istru­zio­ne yield all’interno di un ciclo while per generare numeri pro­gres­si­vi. Quando si raggiunge l’istru­zio­ne 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))

Ot­ti­miz­za­zio­ne di un modello con il ciclo while di Python

L’ot­ti­miz­za­zio­ne dei modelli fa parte del re­per­to­rio standard delle di­sci­pli­ne scien­ti­fi­che. Un modello viene calcolato in base a un insieme di parametri. I parametri vengono quindi regolati e il modello viene calcolato nuo­va­men­te. Una funzione obiettivo viene uti­liz­za­ta per valutare se la modifica dei parametri ha portato a un modello migliore. Se è così, il processo viene ripetuto. In questo modo è possibile trovare ite­ra­ti­va­men­te i parametri ottimali per il modello.

Nor­mal­men­te, il modello si assesta dopo alcune ri­pe­ti­zio­ni, per cui l’avan­za­men­to diventa sempre più piccolo. Se si scende al di sotto di una certa soglia, si in­ter­rom­pe l’ot­ti­miz­za­zio­ne. Per as­si­cu­rar­ci che il ciclo termini, limitiamo anche il numero massimo di ese­cu­zio­ni. Di seguito vi pre­sen­tia­mo un approccio sche­ma­ti­co all’ot­ti­miz­za­zio­ne del modello uti­liz­zan­do 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)

Im­ple­men­ta­re una con­nes­sio­ne in Python con il ciclo while e try-except

Un tentativo di con­nes­sio­ne può non andare a buon fine. Pertanto, è au­spi­ca­bi­le che la con­nes­sio­ne venga stabilita con diversi tentativi. Poiché non possiamo sapere in anticipo quanti tentativi saranno necessari, uti­liz­zia­mo un ciclo while in Python. Inoltre, limitiamo il numero massimo di tentativi. Se nessuno dei tentativi è andato a buon fine, in­ter­rom­pia­mo il processo con un messaggio di errore.

Un approccio sche­ma­ti­co alla soluzione si pre­sen­te­reb­be così: uti­liz­zia­mo un’istru­zio­ne try-except per rilevare un errore durante la creazione di una con­nes­sio­ne. L’uso di un’istru­zio­ne break nel blocco try e di un’istru­zio­ne continue nel blocco except ga­ran­ti­sce un’ite­ra­zio­ne corretta. Se la con­nes­sio­ne fallisce, ri­pro­via­mo con continue. Se la con­nes­sio­ne è 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")

Ite­ra­zio­ne 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’ite­ra­zio­ne di:

  • Elenchi annidati
  • Strutture ad albero
  • Grafici

Vediamo come funziona con l’esempio di una matrioska. Il noto gio­cat­to­lo è co­sti­tui­to da bambole annidate. Il numero di livelli non è visibile dall’esterno. Pro­ce­dia­mo 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.

Mo­del­lia­mo 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, uti­liz­zia­mo un’as­se­gna­zio­ne 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 in­ter­rom­pia­mo l’ite­ra­zio­ne:

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 in­di­pen­den­te­men­te 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.

Ese­cu­zio­ne di un ciclo while in Python su una scac­chie­ra

Uno scenario d’uso comune dei cicli while in Python è quello di spostare un pezzo su una scac­chie­ra. Se si vuole avere la garanzia di visitare tutti i quadrati, sono necessari due cicli annidati. In questo caso, è ne­ces­sa­rio tenere conto del tempo di ese­cu­zio­ne: con aree di grandi di­men­sio­ni, il programma può durare a lungo.

Im­ple­men­tia­mo una semplice “pas­seg­gia­ta casuale”, in cui un pezzo sulla scac­chie­ra viene spostato in modo casuale fino a rag­giun­ge­re una de­sti­na­zio­ne. Un simile schema di movimento può essere ri­scon­tra­to, ad esempio, nel movimento di una par­ti­cel­la in un liquido o di una mosca che vola nello spazio. Poiché non è noto a priori il numero di ite­ra­zio­ni ne­ces­sa­rie, uti­liz­zia­mo un ciclo while in Python.

Definiamo in­nan­zi­tut­to la funzione random_walk() che contiene il ciclo while. Con l’operatore di Python per “is-uguale” con­trol­lia­mo se la posizione corrente è l’obiettivo. In caso contrario, si procede a un’ulteriore ite­ra­zio­ne:

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 au­si­lia­ria get_neighbor() che re­sti­tui­sce 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’im­ple­men­ta­zio­ne della nostra pas­seg­gia­ta casuale:

random_walk(board = (10, 10), goal = (4, 4), start = (5, 7))
Vai al menu prin­ci­pa­le