Python: comprendere e utilizzare il ciclo for
Il ciclo for in Python, “for loop” in inglese, viene utilizzato per eseguire ripetutamente un blocco di codice. I cicli for sono una parte fondamentale della maggior parte dei linguaggi di programmazione. Vi mostriamo come funziona il ciclo for su Python e come utilizzarlo.
- Certificato SSL Wildcard incluso
- Registrazione di dominio sicura
- Indirizzo e-mail professionale da 2 GB
Cos’è il ciclo for su Python?
Oltre all’istruzione if-else, il ciclo for è probabilmente la struttura di programmazione più conosciuta. Generazioni di studentesse e studenti si sono scervellate sul concetto informatico di ciclo, in quanto inizialmente non risulta molto intuitivo. Tuttavia, i problemi di comprensione possono derivare più che altro dal modo in cui il materiale viene presentato. Di per sé, infatti, i cicli non sono nulla di eccezionale.
Vi illustriamo il concetto di ciclo con un esempio. Immaginiamo una classe delle elementari. L’insegnante vuole determinare l’altezza media delle bambine e dei bambini con un esperimento. Per farlo, chiede a ogni bambina e bambino la loro altezza e somma le singole altezze per ottenere un totale. Poi divide la somma per il numero degli alunni della classe per ricavare l’altezza media. Astraiamo un semplice algoritmo dalla procedura dell’insegnante:
- Richiesta dell’altezza di ciascun bambino e bambina e aggiunta al totale corrente
- Divisione della somma per il numero di alunni della classe
Il primo passaggio viene eseguito una volta per ogni bambino e bambina e dipende quindi dal numero di studenti nella classe. Il secondo viene eseguito una sola volta, indipendentemente dalle dimensioni della classe. Abbiamo bisogno di un ciclo per il primo passaggio. Di seguito riportiamo quindi un esempio di codice che calcola l’altezza media di un bambino o una bambina della scuola con un ciclo for su Python:
children_heights = [155, 171, 148, 161, 158, 153, 162]
height_sum = 0
for height in children_heights:
height_sum = height_sum + height
average_height = height_sum // len(children_heights)
print(average_height)
Il ciclo for su Python esegue ripetutamente un blocco di codice. Questa operazione viene anche definita “iterazione”. In particolare, i cicli consentono a diversi elementi combinati in un “insieme” di essere elaborati singolarmente secondo lo stesso schema. Si usa un ciclo for quando la dimensione dell’insieme può essere determinata in fase di esecuzione del programma. In caso contrario, di solito si utilizza un ciclo while.
Imparate da soli a scrivere nel codice Python grazie al nostro tutorial su Python.
Cosa distingue il ciclo for su Python dagli altri linguaggi?
Molti linguaggi di programmazione conoscono il concetto di ciclo for. Oltre che in Python, è una struttura di base anche in altri linguaggi come C, Java, JavaScript e PHP. I linguaggi puramente funzionali come Haskell o Lisp di solito riescono a fare a meno di un ciclo for esplicito. Al posto dell’iterazione, in questi linguaggi si utilizzano funzioni ricorsive.
Tutti i cicli hanno in comune il fatto che un blocco di codice viene eseguito ripetutamente. Tuttavia, il meccanismo d’azione del ciclo for in Python differisce notevolmente da quello di altri linguaggi. Ad esempio, la maggior parte dei linguaggi di programmazione utilizza una cosiddetta variabile di ciclo che viene incrementata o decrementata durante l’esecuzione del ciclo.
Operazione | Significato | Sintassi convenzionale | Sintassi Python |
---|---|---|---|
Incremento | Aumenta il valore di una variabile intera di una quantità specifica e fissa | i++ | index += 1 |
Decremento | Diminuisce il valore di una variabile intera di una quantità specifica e fissa | i-- | index -= 1 |
Vediamo un esempio di come funziona un ciclo for in altri linguaggi. Con un ciclo for, produciamo i numeri da 0 a 9 in JavaScript. Definiamo una variabile intera “number” e la incrementiamo finché il numero in essa contenuto è inferiore a 10. Il codice utilizzato a questo scopo sembra piuttosto criptico per i nuovi arrivati:
for ( let number = 0; number < 10; number++ ) {
console.log(number);
}
Il codice per un ciclo for corrispondente in Python appare molto più ordinato:
for number in range(10):
print(number)
Invece di fornire direttamente il valore della variabile del ciclo, di solito viene utilizzato per indicizzare un elemento all’interno di un insieme. Di nuovo, prima un esempio in JavaScript: produciamo i nomi contenuti nell’elenco “people” uno dopo l’altro. Utilizziamo la variabile del ciclo ‘i’ come indice progressivo dei singoli elementi:
people = ['Jack', 'Jim', 'John']
for (let i = 0; i < people.length; i++) {
console.log("Here comes " + people[i]);
}
L’indicizzazione diretta di elementi successivi dell’elenco deve essere trattata con cautela. Questo perché un tentativo di accesso al di fuori dei limiti consentiti provoca un errore di runtime. Di norma, si tratta del famoso “off-by-one error”. Python dimostra che si può procedere in modo diverso. Qui vi proponiamo lo stesso esempio con un ciclo for su Python, in cui iteriamo direttamente gli elementi dell’elenco senza indicizzarli con una variabile del ciclo:
people = ['Jack', 'Jim', 'John']
for person in people:
print(f"Here comes {person}")
L’uso indiretto della variabile del ciclo contribuisce a creare molta confusione quando si imparano i cicli for in altri linguaggi. Questo perché di solito non siamo interessati alla variabile centrale del ciclo. Viene utilizzata solo per indicizzare i singoli elementi. L’uso dei cicli for convenzionali richiede la comprensione di diverse questioni complesse. Vediamone un esempio con JavaScript:
Argomento | Occorrenza nel ciclo for di JavaScript |
---|---|
Assegnazione delle variabili | let i = 0 |
Espressioni booleane | i < limit |
Operatore di incremento o decremento | i++ / i-- |
Determinazione delle dimensioni di un insieme | i < list.length |
Indicizzazione degli elementi basata su zero | i < list.length ist OK; i <= list.length führt zu Off-by-one Error |
Per chiarire meglio, vediamo il risultato delle singole lettere di una parola. All’interno del ciclo for, una lettera della parola viene resa disponibile nella variabile “letter” per ogni passaggio del ciclo. Questo funziona senza una variabile di ciclo e quindi senza il rischio di produrre un errore off-by-one. Il codice è preciso e di facile lettura:
word = "Python"
for letter in word:
print(letter)
Come funziona un ciclo for in Python?
Come abbiamo visto, il ciclo for su Python risolve elegantemente il problema dell’iterazione degli elementi di un insieme. Possiamo farlo senza deviazioni attraverso una variabile numerica di ciclo. È sicuramente una buona cosa, ma come funziona esattamente? Per comprendere il principio di funzionamento del ciclo for in Python, è necessario conoscere i concetti di iterabile e iteratore.
Cosa sono l’iterabile, l’iteratore e il generatore?
Il ciclo for in Python opera su oggetti noti come “iterabili”. In questo caso si tratta di stringhe, liste, tuple e altri tipi di dati composti. Usando le parole della documentazione ufficiale di Python:
“[An iterable is] an object capable of returning its members one at a time” - Fonte: https://docs.python.org/3/glossary.html#term-iterable
Traduzione: “[Un iterabile è] un oggetto che ha la capacità di restituire i suoi membri uno alla volta” (tradotto da IONOS).
Un oggetto iterabile ha due proprietà:
- Raggruppa diversi elementi in un insieme.
- Garantisce l’accesso agli elementi tramite un’interfaccia chiamata “iteratore”.
Nel complesso, ciò significa che gli iterabili sono insiemi il cui contenuto può essere iterato. In particolare, un iterabile ha un metodo “__iter__()” che restituisce un iteratore. L’iteratore è un oggetto che restituisce l’elemento successivo dell’iterabile su comando. Inoltre, un iteratore ricorda la posizione dell’ultimo elemento restituito all’interno dell’insieme.
Funzione | Spiegazione | Esempio |
---|---|---|
iter(collection) | Richiama il metodo __iter__() dell’insieme | it = iter("Python") |
next(iter) | Richiama il metodo __next__() dell’iteratore | next(it) |
collection[index] | Richiama il metodo __getitem__(index) dell’insieme | 'Python'[1] |
Un iteratore restituisce l’elemento successivo quando viene richiamato il metodo __next__(). Se tutti gli elementi dell’insieme sono stati consegnati, l’iteratore è esaurito. Un’altra chiamata a __next__() attiva un’eccezione “StopIteration”.
Vediamo come funziona un iteratore con un esempio. Creiamo un oggetto range() che rappresenta i numeri consecutivi da 21 a 23. Successivamente, creiamo un iteratore con la funzione iter() e otteniamo gli elementi successivi con la funzione next(). Con l’ultima chiamata, viene avviata l’eccezione perché l’iteratore è esaurito:
numbers = range(21, 24)
number = iter(numbers)
next(number)
# returns `21`
next(number)
# returns `22`
next(number)
# returns `23`
next(number)
# raises `StopIteration` exception
Un iteratore consente di accedere ai singoli elementi di un insieme. Python conosce anche il concetto correlato di “generatore”. La differenza è che un generatore crea i singoli elementi solo quando vi si accede. In questo modo si risparmia spazio in memoria durante l’esecuzione del programma; questa operazione viene definita anche “lazy generation”.
Un generatore in Python si basa su una funzione che utilizza l’istruzione yield. Simile all’istruzione return, restituisce un oggetto e termina la chiamata di funzione. Quando viene richiamata, tuttavia, la funzione generatore non riparte dall’inizio, ma continua dopo l’ultima istruzione yield.
Vediamo un esempio. Scriviamo la nostra implementazione della funzione range(). Per farlo, utilizziamo l’istruzione yield all’interno di un ciclo while per generare numeri progressivi:
def my_range(start, stop):
if stop < start:
return None
current = start
while current < stop:
yield current
current += 1
# test
assert list(my_range(7, 9)) == list(range(7, 9))
Saltare e annullare un ciclo for in Python
In pratica, a volte è necessario saltare un singolo passaggio del ciclo. Come molti altri linguaggi, Python contiene l’istruzione continue. Quando viene richiamato continue all’interno del corpo del ciclo, l’iterazione corrente viene interrotta. Il ciclo inizia immediatamente l’iterazione successiva.
L’istruzione continue può essere utilizzata in modo simile a quella Early Return in una chiamata di funzione. Ad esempio, saltiamo un’iterazione non appena stabiliamo che un set di dati non ha la qualità richiesta:
def process_data(data):
for data_set in data:
data_set.validate()
# early continue after cheap check fails
if not data_set.quality_ok():
continue
# expensive operation guarded by early continue
data_set.process()
Un altro esempio. Produciamo un testo e saltiamo tutte le lettere in seconda posizione:
text = 'Skipping every second letter'
for index, letter in enumerate(text):
if index % 2 != 0 and letter != ' ':
continue
print(letter)
L’istruzione break viene spesso utilizzata per implementare algoritmi di ricerca. Se un elemento cercato è stato trovato all’interno di un ciclo, non è necessario iterare ulteriormente. Analogamente alla funzione any(), controlliamo un elenco per verificare la presenza di un singolo valore true. Interrompiamo con break non appena abbiamo trovato qualcosa:
bool_list = [False, False, True, False]
for index, boolean in enumerate(bool_list):
if boolean:
print(f"Value at position {index + 1} is True")
print(f"Aborting inspection of remaining {len(bool_list) - index - 1} item(s)")
break
In relazione all’istruzione break, un ciclo for in Python può essere dotato di un corpo opzionale else. Il codice che contiene viene eseguito quando il ciclo termina senza che sia stata eseguita un’istruzione break:
def find_element(target, collection):
for element in collection:
if element == target:
print("Found what you're looking for")
break
else:
print("Didn't find what you were looking for")
# test
find_element('a', 'Python')
find_element('o', 'Python')
I cicli for sono spesso utilizzati in Python all’interno dei corpi delle funzioni. In questo caso, è comune utilizzare un’istruzione return invece di una break. Il nostro algoritmo di ricerca si riformula senza l’uso di break ed else:
def find_element(target, collection):
for element in collection:
if element == target:
print("Found what you're looking for")
# returning breaks us out of the loop
return element
# we made it here without returning
print("Didn't find what you were looking for")
return None
# test
print(find_element('a', 'Python'))
print(find_element('o', 'Python'))
Quali sono le migliori pratiche per i cicli for in Python?
I cicli for in Python sono usati principalmente per iterare gli elementi di una sequenza o di un insieme. Inoltre, per molti casi d’uso comuni esistono metodi più diretti. Vi presentiamo le migliori pratiche principali e anti-pattern. Innanzitutto, un riepilogo dei termini centrali:
Termine | Spiegazione | Esempio |
---|---|---|
Insieme | Riassunto di più elementi. Un insieme è un iterabile | ('Walter', 'White'), [4, 2, 6, 9], 'Python' |
Iteratore | Interfaccia per iterare gli insiemi | it = iter('Python') |
Generatore | Una funzione che utilizza yield al posto dell’istruzione return. Un generatore è un iterabile | range(10) |
Comprensione | Espressione iterante; genera un nuovo insieme basato su un iterabile | [num ** 2 for num in range(10)] |
Iterazione diretta degli elementi di un insieme
Un errore comune dei programmatori Python inesperti è l’uso improprio del ciclo for in Python. Come è comune in altri linguaggi, si usa la funzione len() come limite di quella range() per creare una variabile numerica del ciclo. In questo modo indicizzate i singoli elementi dell’insieme:
word = 'Python'
for i in range(len(word)):
print(word[i])
Questo anti-pattern è disapprovato in quanto non è considerato in linea con la sintassi di Python per una buona ragione. Infatti, è meglio iterare direttamente gli elementi dell’insieme con il ciclo for in Python:
word = 'Python'
for letter in word:
print(letter)
Enumerare gli elementi di un insieme con enumerate() includendo l’indice
A volte è necessario l’indice di un elemento all’interno dell’insieme. Invece di creare l’indice come variabile del ciclo, utilizziamo la funzione enumerate(). Viene così restituita la tupla (indice, elemento). Si noti che l’indice inizia a contare da zero:
names = ["Jim", "Jack", "John"]
for index, name in enumerate(names):
print(f"{index + 1}. {name}")
Iterazione di tuple di elementi con la funzione zip()
Un altro scenario comune è quello di iterare contemporaneamente gli elementi di due insiemi di uguale lunghezza. L’approccio di Python utilizza la funzione zip(), prendendo due insiemi di uguale lunghezza e restituendo 2 tuple successive:
people = ('Jim', 'Jack', 'John')
ages = (42, 69, 13)
# ascertain both collections are same length
assert len(people) == len(ages)
# iterate over tuples of (person, age)
for person, age in zip(people, ages):
print(f"{person} is {age} years old")
Creazione di una variabile di ciclo numerica con la funzione range()
Normalmente, i cicli for vengono utilizzati in Python per iterare gli elementi di un insieme. L’incremento di un intero con un ciclo for è un caso particolare in Python. Il modo corretto è quello di costruire un oggetto range con la funzione range() e iterarlo:
for counter in range(10):
print(counter)
Usare l’operatore in per verificare se un insieme contiene un elemento
Trovare un elemento particolare all’interno di un insieme fa parte del repertorio standard di un programmatore o di una programmatrice. Normalmente, si utilizza una funzione che itera gli elementi, verificando l’uguaglianza di ciascun elemento con quello cercato. Se l’elemento viene trovato, l’iterazione viene interrotta.
In Python, per questo caso comune esiste l’operatore in. L’operatore verifica se l’insieme contiene l’elemento cercato e restituisce un valore booleano corrispondente:
'a' in 'Python'
'y' in 'Python'
Utilizzo della funzione list() per creare un elenco da un iterabile
A differenza di molti altri linguaggi, in Python non è necessario utilizzare un ciclo for per scrivere le lettere di una stringa una per una in un elenco. Si usa invece la funzione list() per convertire un iterabile in un elenco di elementi. Analizziamo entrambi gli approcci con un esempio. Iteriamo le lettere di una parola e le aggiungiamo a un elenco vuoto:
word = 'Python'
letters = []
for letter in word:
letters.append(letter)
Possiamo risparmiarci la fatica. Creiamo l’elenco direttamente con la funzione list(). Allo stesso tempo, verifichiamo con l’asserzione assert che entrambi i metodi diano lo stesso risultato:
assert list(word) == letters
Un altro esempio. Creiamo un elenco di numeri da zero a nove. Un oggetto range serve come base per un iterabile:
list(range(10))
Oltre agli elenchi, a partire da un iterabile si possono creare anche dei set. Ad esempio, creiamo un set che restituisce le lettere contenute in una frase. Con l’operatore in verifichiamo che il set delle lettere non contenga una ‘a’:
alphabet = set('Python is not hyped')
assert 'a' not in alphabet
Sostituire i cicli for in Python con le comprensioni
Un uso comune dei cicli for in Python è quello di modificare gli elementi di un insieme. Potremmo voler calcolare nuovi valori in base a un insieme o filtrare alcuni elementi in base a un modello. Seguendo lo stile di programmazione imperativo, descriviamo i singoli passaggi nel modo seguente:
- Iterare l’insieme con un ciclo for
- Elaborare ogni elemento
- Se necessario, combinare un sottoinsieme di elementi in un nuovo insieme
Per le modifiche semplici, è piuttosto complesso. I linguaggi funzionali dimostrano che è possibile farlo in modo più semplice. Fortunatamente, Python conosce il concetto di “comprensione” (“comprehension” in inglese). Le comprensioni possono sostituire semplici applicazioni del ciclo for in Python. Sono più performanti di applicazioni equivalenti di un ciclo for.
Una comprensione crea un insieme modificato basato su un iterabile. Per farlo, viene utilizzata una sintassi concisa ed espressiva. Vediamo la sintassi generale della comprensione di una lista. Scriviamo l’espressione tra parentesi quadre. Viene eseguita un’operazione sugli elementi di un insieme; ogni elemento viene copiato in un nuovo elenco:
[ operation(element) for element in collection ]
Inoltre, gli elementi possono essere filtrati in base a determinati modelli. Vengono utilizzati un if opzionale e una condizione:
[ operation(element) for element in collection if condition(element) ]
Vediamo ora un esempio di ciclo for in Python che può essere sostituito da una comprensione. Abbiamo un elenco di numeri e vogliamo calcolare il corrispondente elenco di numeri quadrati:
numbers = [2, 3, 5, 9, 17]
Creiamo un elenco vuoto e lo riempiamo con i quadrati all’interno di un ciclo for:
squares = []
for number in numbers:
squares.append(number ** 2)
L’elenco dei numeri quadrati può essere espresso più semplicemente sotto forma di comprensione:
squares_comp = [number ** 2 for number in numbers]
Utilizziamo quindi l’istruzione assert per assicurarci che entrambi i metodi restituiscano lo stesso risultato:
assert squares == squares_comp
Un altro esempio. Desideriamo estrarre le lettere minuscole da una stringa. Creiamo un elenco di lettere sia maiuscole che minuscole da inserire:
word = list("PyThoN")
Il modo convenzionale per estrarre le lettere minuscole è quello di iterare le lettere. Verifichiamo ogni lettera con la funzione islower() e la aggiungiamo a un elenco inizialmente vuoto se il risultato è positivo:
lowers = []
for letter in word:
if letter.islower():
lowers.append(letter)
Possiamo risparmiarci questo ciclo for in Python. Utilizziamo invece una comprensione che copia solo le lettere minuscole dell’elenco originale:
lowers_comp = [ letter for letter in word if letter.islower() ]
Anche in questo caso, verifichiamo l’uguaglianza dei due metodi utilizzando l’istruzione assert:
assert lowers == lowers_comp