ProgrammationDéveloppeur Backend

Expliquez le protocole 'Sequence' en Python, comment l'implémenter et en quoi il diffère des objets simplement itérables ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

En Python, le protocole 'Sequence' définit une interface pour les objets qui peuvent être indexés et itérés, comme les listes ou les tuples. Historiquement, les séquences existent depuis presque les premières versions de Python pour prendre en charge les opérations naturelles sur les structures de données : indexation, tranches, itération des éléments.

Problème — pour qu'une classe utilisateur se comporte comme une séquence, il ne suffit pas d'avoir les méthodes iter et next. Un support complet du comportement de séquence nécessite des méthodes supplémentaires.

Solution — pour implémenter votre propre type de séquence, vous devez définir les méthodes getitem (nécessaire pour l'indexation et les tranches) et éventuellement len (pour len() et vérifier la longueur). Ainsi, l'objet prendra en charge l'itération, l'accès par index, le travail avec des tranches, ainsi que de nombreuses opérations standard de Python avec des séquences.

Exemple de code :

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

Caractéristiques clés :

  • L'objet prend en charge l'accès par index et les tranches via getitem.
  • Pour prendre en charge la fonction len(), len est nécessaire.
  • L'itération est construite sur getitem, et non sur iter, si iter n'est pas défini.

Questions pièges.

Si j'implémente seulement iter et next, mon objet sera-t-il une séquence (Sequence) ?

Non. Un tel objet ne sera qu'itérable (iterable), mais pas une séquence. Il ne prendra pas en charge l'indexation, les tranches, les fonctions standard des objets semblables à des listes.

Est-il nécessaire d'implémenter getitem pour prendre en charge la boucle for ?

Ce n'est pas nécessaire. Si iter est implémenté, for fonctionnera. Mais s'il n'y a pas iter, l'interpréteur essaiera d'utiliser getitem, en commençant par l'index 0, jusqu'à ce qu'une IndexError se produise. Ainsi, pour une séquence, getitem est suffisant.

Peut-on implémenter getitem uniquement pour int, et pas pour slice ?

Techniquement, l'objet fonctionnera pour c[0], mais essayer de prendre une tranche c[1:4] échouera. Pour prendre en charge les tranches, getitem doit pouvoir traiter les objets de type slice (voir slice.indices et isinstance(key, slice)).

Exemple de code :

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

Erreurs typiques et anti-modèles

  • Ils ne définissent que iter, en s'attendant à ce que l'objet devienne une séquence complète.
  • Ils implémentent getitem, mais ne traitent pas les objets slice.
  • Ils n'implémentent pas len, ce qui provoque un TypeError lors de l'appel de len(obj).

Exemple de la vie réelle

Cas négatif

Ils ont implémenté une structure personnalisée en ne définissant que iter, pensant qu'ils pouvaient désormais utiliser des tranches et de l'indexation.

Avantages :

  • Fonctionne dans la boucle for et prend en charge les générateurs.

Inconvénients :

  • Pas de support pour la syntaxe obj[5] ou obj[1:3], provoque une erreur.
  • len(obj) ne fonctionne pas non plus.

Cas positif

La classe implémente getitem avec prise en charge des tranches et des index int, ainsi que len.

Avantages :

  • Toutes les opérations de séquence sont prises en charge : tranches, indexation, len, itération.
  • Intégration avec la bibliothèque standard (par exemple, random.choice).

Inconvénients :

  • Il est nécessaire de mettre en œuvre avec soin le traitement des tranches, sinon des bogues peuvent survenir lors de l'utilisation des tranches.