ProgrammationDéveloppeur Backend

Expliquez le fonctionnement de la fonction enumerate() en Python. Comment l'utiliser pour parcourir correctement les éléments et les indices d'une séquence, et quelles caractéristiques importantes de son utilisation faut-il prendre en compte ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

Historique de la question : La fonction enumerate() est apparue dans Python 2.3 et est maintenant le moyen standard d'accéder simultanément à l'élément et à l'indice de l'élément lors du parcours des collections. Avant l'apparition de enumerate(), les programmeurs créaient souvent leurs propres compteurs ou utilisaient la fonction range(len(sequence)), ce qui était peu pratique et peu lisible.

Problème : Une boucle for classique ne parcourt que les valeurs. Pour accéder à l'indice, on utilise souvent range(len(...)), ce qui ne fonctionne pas avec tous les objets itérables (par exemple, les générateurs, les chaînes de caractères et les tuples de longueur variable, ainsi que lors de la filtration). Cela conduit à des erreurs et complique le code.

Solution : enumerate() renvoie des paires (indice, élément), ce qui permet d'obtenir l'indice de l'élément courant même pour des collections non standard ou des générateurs filtrés. La fonction accepte un deuxième argument optionnel — la valeur de départ du compteur.

Exemple de code :

words = ['apple', 'banana', 'cherry'] for idx, word in enumerate(words, 1): print(f"{idx}: {word}") # Sortie : # 1: apple # 2: banana # 3: cherry

Caractéristiques clés :

  • Fonctionne avec tous les objets itérables, et pas seulement avec des listes ou des structures indexables.
  • Permet de définir un indice de départ (par exemple, commencer la numérotation à 1).
  • Renvoie un itérateur, et non une liste (c'est-à-dire qu'elle n'utilise pas de mémoire supplémentaire).

Questions pièges.

Pourquoi utilise-t-on souvent enumerate dans les fonctions au lieu de range(len(seq)) ?

Réponse : range(len(seq)) ne fonctionne qu'avec des séquences accessibles par index et ne prend pas en compte les changements de longueur pendant l'itération. De plus, il est moins lisible et fonctionne moins bien ou pas du tout pour les générateurs. enumerate() offre un accès sûr aux paires indice-valeur pour toute collection itérable.

Exemple de code :

# Ne fonctionne pas avec un générateur : gen = (x for x in range(5)) for i in range(len(gen)): print(i) # Erreur : un générateur n'a pas de longueur

Peut-on utiliser enumerate pour modifier les éléments d'une liste pendant la traversée ?

Réponse : Oui, mais il faut itérer par indices pour écrire les valeurs. Sinon, si l'on itère seulement sur les valeurs, vous modifiez une copie de l'objet et non l'original.

Exemple de code :

nums = [1, 2, 3] for idx, val in enumerate(nums): nums[idx] = val * 2 # nums = [2, 4, 6]

Que renverra enumerate si on lui passe un objet qui change pendant l'itération ?

Réponse : Si la collection change pendant la traversée (par exemple, des éléments sont supprimés), le comportement peut être inattendu, car enumerate suit l'itérateur interne, qui peut se désynchroniser. Il est donc déconseillé de modifier la collection pendant l'itération.

Erreurs typiques et anti-patterns

  • Utilisation de range(len(...)) pour des objets sans longueur ou pour ceux dont la longueur peut changer.
  • Mauvaise gestion de l'indice de départ lorsque cela est critique (par exemple, pour le vingt et unième siècle, le comptage ne commence pas à zéro).
  • Tentative de modifier la taille de la collection pendant la traversée avec enumerate.

Exemple de la vie réelle

Cas négatif

Un programmeur parcourt une liste en utilisant range(len(list)) et supprime des éléments en cours de route. Résultat : les indices sont décalés, certains éléments sont sautés.

Avantages :

  • On peut accéder directement à l'indice.

Inconvénients :

  • Erreurs d'indexation, code peu lisible, possibité d'out of range ou de perte d'éléments.

Cas positif

On utilise enumerate(), tout en formant une nouvelle liste des éléments requis ou en modifiant une valeur par indice, mais la taille de la liste ne change pas dans la boucle.

Avantages :

  • Code propre, fonctionnement fiable avec toutes les collections, moins de bugs.

Inconvénients :

  • Peut nécessiter mémoire supplémentaire si une copie de la liste doit être créée.