Iterabile

L’iterabile è di base un contenitore di oggetti, e dà accesso a tutti gli oggetti in sequenza. La qualità di un iterabile è la temporalità, e non la spazialità, perchè non sevre avere un oggetto con tutti i valori, ma i valori possono essere generati quando servono, uno alla volta.

range

L’oggetto range è un iterabile, e rappresenta un intervallo di interi. range([iniziale], limite, [incremento]) Il valore iniziale di default è 0, e quello di incremento è +1. Il limite è escluso dall’intervallo.

Trasformazione in tupla o lista

Un iterabile si può trasformare facilmente in una tupla o in una lista tramite due metodi list(i) e tuple(i). Un esempio pratico può essere:

list(range(10))			# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 
tuple(range(5))			# (0, 1, 2, 3, 4)
title: I file sono iterabili
Un file aperto è un iterabile, e itera sulle righe.

Un iterabile, però, deve avere una controparte computazionale, chiamata iteratore.

Iteratore

Un iteratore è una struttura che sa come generare/recuperare gli elementi del suo iterabile.

iteratore = iter(R) o iteratore = R.__iter__(), con R un iterabile, ritorna un iteratore. next(iteratore) o iteratore.__next__() restituisce l’elemento successivo. Se l’iterabile è arrivato in fondo, solleva un errore StopIteration, che si può gestire con un catch.

Ovviamente id(iteratore) == id(R) produce falso, perchè sono due oggetti completamente differenti.

La struttura for in realtà non è altro che zucchero sintattico per una struttura un pelo più lunga da scrivere, ovvero il while:

while True:
	try:
		e = next(I)
	except StopIteration:
		break
	else:
		print(e)
 

Di seguito una implementazione custom di un iterabile con annesso iteratore.

class my_range:
    class my_range_iterator:
        def __init__(self, start,end,step):
            self.s=start
            self.e=end
            self.p=step
        def __next__(self):
            if (self.p>0 and self.s>=self.e) or \
               (self.p<0 and self.s<=self.e):
                raise StopIteration
            val = self.s
            self.s += self.p
            return val
          
    def __init__(self,*args):
        '''
           Un oggetto my range deve essere inizializzato un range,
           e dunque con un numero di parametri variabile fra 1 e 3
        '''
        if (n:=len(args)) and n>3:
            raise TypeError(f"my_range expected at most 3 arguments, got {n}")
        
        for x in args:
            if type(x) is not int:
                raise TypeError(f"'{type(x)}' object cannot be interpreted as an integer")
        
        if n==1:
            self.start = 0
            self.end = args[0]
            self.step = 1
        else:
            self.start = args[0]
            self.end = args[1]
            self.step = 1 if n==2 else args[2]
        
    def __iter__(self):
        return my_range.my_range_iterator(self.start,self.end,self.step)
Un oggetto può fare contemporaneamente sia da iterabile che da iteratore.

python_book5