ProgrammazioneSviluppatore Backend

Spiega il protocollo 'Sequence' in Python, come implementarlo e in cosa si differenzia dagli oggetti iterabili?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

In Python, il protocollo 'Sequence' definisce un'interfaccia per oggetti che possono essere indicizzati e iterati, come ad esempio liste o tuple. Storicamente, le sequenze sono emerse praticamente dalle prime versioni di Python per supportare operazioni naturali con le strutture dati: indicizzazione, slicing, iterazione degli elementi.

Problema — affinché una classe utente si comporti come una sequenza, non bastano i metodi iter e next. Per supportare completamente il comportamento delle sequenze sono necessari metodi aggiuntivi.

Soluzione — per implementare il proprio tipo di sequenza, è necessario definire il metodo getitem (necessario per l'indicizzazione e gli slicing) e opzionalmente len (per len() e controllo della lunghezza). In questo modo, l'oggetto supporterà l'iterazione, l'accesso per indice, il lavoro con gli slicing, oltre a molte operazioni standard di Python con le sequenze.

Esempio di codice:

class MyCounter: def __init__(self, stop): self._stop = stop def __getitem__(self, index): if 0 <= index < self._stop: return index * 10 else: raise IndexError('Fuori intervallo') def __len__(self): return self._stop c = MyCounter(5) print(c[3]) # 30 print(len(c)) # 5 for x in c: print(x)

Caratteristiche chiave:

  • L'oggetto supporta l'accesso per indice e gli slicing tramite getitem.
  • Per supportare la funzione len() è necessario len.
  • L'iterazione si basa su getitem, e non su iter, se iter non è definito.

Domande trabocchetto.

Se implemento solo iter e next, il mio oggetto diventa una sequenza (Sequence)?

No. Tale oggetto sarà solo iterabile (iterable), ma non una sequenza. Non supporterà l'indicizzazione, gli slicing, le funzioni standard degli oggetti simili a liste.

È necessario implementare getitem per supportare il ciclo for?

Non è necessario. Se iter è implementato, il for funzionerà. Ma se non c'è iter, l'interprete proverà a utilizzare getitem, iniziando dall'indice 0, fino a quando non si verificherà un IndexError. Pertanto, per le sequenze è sufficiente getitem.

Posso implementare getitem solo per int, e non per slice?

Tecnicamente, l'oggetto funzionerà per c[0], ma il tentativo di fare uno slicing c[1:4] fallirà. Per supportare gli slicing, getitem deve essere in grado di gestire oggetti di tipo slice (vedere slice.indices e isinstance(key, slice)).

Esempio di codice:

class S: def __getitem__(self, idx): if isinstance(idx, slice): return [x for x in range(idx.start or 0, idx.stop or 10, idx.step or 1)] return idx * 2

Errori comuni e anti-pattern

  • Definiscono solo iter, sperando che l'oggetto diventi una vera sequenza.
  • Implementano getitem, ma non gestiscono gli oggetti slice.
  • Non implementano len, il che causa TypeError quando si chiama len(obj).

Esempio dalla vita reale

Caso negativo

Hanno implementato una struttura personalizzata definendo solo iter, pensando che ora potessero usare slicing e indicizzazione.

Pro:

  • Funziona nel ciclo for e supporta i generatori.

Contro:

  • Nessun supporto per la sintassi obj[5] o obj[1:3], genera un errore.
  • len(obj) non funziona nemmeno.

Caso positivo

La classe implementa getitem con supporto per lo slicing e gli indici int, nonché len.

Pro:

  • Supporta tutte le operazioni delle sequenze: slicing, indicizzazione, len, iterazione.
  • Integrazione con la libreria standard (ad esempio, random.choice).

Contro:

  • È necessario implementare attentamente la gestione dello slice, altrimenti potrebbero verificarsi bug durante il lavoro con gli slicing.