5.1 Iterable
e Iterator
Come già sai, gli iteratori sono oggetti che implementano il protocollo dell'iteratore, permettendo di ottenere gli elementi di una collezione uno per uno. Gli iteratori sono ampiamente usati in Python per scorrere gli elementi di sequenze come liste, tuple e stringhe.
Vediamo come sono strutturati gli iteratori e come utilizzarli.
Oggetto Iterabile (Iterable)
Affinché un oggetto possa essere attraversato con un ciclo for
, deve
essere iterabile – un Iterable
. Ciò significa che il nostro oggetto deve
implementare il metodo __iter__()
,
che restituisce un oggetto-iteratore.
Oggetto-iteratore (Iterator)
È un oggetto speciale che possiede
la funzione __next__()
per restituire
l'elemento successivo della sequenza. Quando gli elementi
finiscono, il metodo __next__()
lancia l'eccezione
StopIteration
come segnale di fine iterazione.
L'iteratore deve anche implementare il metodo __iter__()
,
che restituisce l'iteratore stesso.
Esempio usando funzioni integrate di Python
In questo esempio, la lista
numbers è un oggetto iterabile.
Otteniamo l'iteratore usando la funzione
iter()
e usiamo la funzione
next()
per scorrere gli elementi fino a quando
non viene lanciata l'eccezione
StopIteration
.
# Oggetto iterabile
numbers = [1, 2, 3, 4, 5]
# Otteniamo l'iteratore dall'oggetto iterabile
iterator = iter(numbers)
# Usiamo l'iteratore per scorrere gli elementi
try:
while True:
number = next(iterator)
print(number)
except StopIteration:
pass
Proprio quello che succede quando scrivi il codice tipo:
# Oggetto iterabile
numbers = [1, 2, 3, 4, 5]
for number in numbers:
print(number)
5.2 L'essenza dell'iteratore
Un iteratore è un certo oggetto che ci aiuta a scorrere un gruppo di elementi uno per uno. Le implementazioni possono essere molto diverse. Scriviamo la nostra classe, dove realizziamo tutti i requisiti richiesti da un iteratore.
Passo 1. Creiamo la nostra classe
Facciamola restituire i numeri da start
a
end
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
Passo 2. Supporto della funzione __iter__
Ora dobbiamo aggiungere la funzione __iter__
,
che restituirà l'oggetto-iteratore su cui verrà chiamata
la funzione __next()__
. Stiamo tornando
al nostro stesso oggetto – questo non è vietato.
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
Passo 3. Supporto della funzione __next__
Ora dobbiamo aggiungere al nostro oggetto iteratore la funzione __next__
, che restituirà
l'elemento successivo della nostra lista. Usiamo semplicemente la variabile current
:
def __next__(self):
current = self.current
self.current += 1
return current
Passo 4. Fermare l'iteratore
Se l'iteratore ha già restituito tutti i valori previsti, dovrebbe lanciare l'eccezione StopIteration
. Aggiustiamo
un po' la nostra ultima funzione:
def __next__(self):
if self.current >= self.end:
raise StopIteration
current = self.current
self.current += 1
return current
Ottimo. Ora possiamo usare il nostro iteratore. Ecco un esempio del nostro intero codice:
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
current = self.current
self.current += 1
return current
# Creiamo un'istanza di un iteratore personalizzato
my_iter = MyIterator(1, 5)
# Usiamo l'iteratore per scorrere gli elementi
for num in my_iter:
print(num)
5.3 Un iteratore corretto
Cosa c'è che non va nell'iteratore dell'esempio precedente? Sì, è un iteratore, funziona, ma è troppo semplice. Non consente di scorrere la stessa collezione di elementi contemporaneamente con diversi iteratori.
Sarebbe più corretto scrivere del codice che non restituisse un riferimento a se stesso nel metodo __iter__
, ma
un oggetto separato, che restituisse correttamente tutti gli elementi.
Esempio:
class MyIterable:
def __init__(self, data):
self.data = data
def __iter__(self):
return MyIterator(self.data)
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
item = self.data[self.index]
self.index += 1
return item
# Utilizzo
my_iterable = MyIterable([1, 2, 3, 4])
for item in my_iterable:
print(item)
In questo esempio abbiamo due classi - la prima prende una collezione, sulla quale intendiamo iterare. E
la seconda è l'iteratore, che restituisce gli elementi della collezione nel metodo next()
. Anche se è abbastanza semplice,
è proprio così che dovresti aggiungere iteratori alle tue classi.
GO TO FULL VERSION