Mon Labo Python

Comment utiliser enumerate en python pour gérer l'index de ma boucle for

Comment utiliser enumerate en python pour gérer l'index de ma boucle for

Ma liste de courses s’affichait en vrac dans la console, et je voulais juste la présenter proprement avec un numéro devant chaque article. Au lieu de ça, mon terminal m’a sorti trois fois le chiffre 1 d’affilée avant de se taire. C’était tard, la table était encombrée, et mon compteur bricolé venait encore de m’échapper.

Ce fameux réflexe du compteur manuel qui déraille

Un mardi soir de mai, après environ six semaines de formation en ligne suivie tant bien que mal après le travail, j’avais l’impression d’avoir enfin dompté les listes. Parcourir des éléments un par un avec une boucle for me semblait presque naturel. On écrit for element in liste:, on affiche l’élément, et l’ordinateur s’exécute sans broncher. C’est reposant, un peu comme égrapper des groseilles : une baie après l’autre, jusqu’au bout de la branche.

Les ennuis ont commencé le jour où j’ai voulu savoir où j’en étais pendant le défilé. Pas seulement voir défiler pommes, pain, lait, mais afficher une liste ordonnée, comme sur un bloc-notes d’épicerie. Mon tout premier réflexe de profane a été de poser une variable témoin juste au-dessus de ma boucle, comme on garderait le doigt sur une page pour ne pas perdre sa ligne :

i = 0
for article in panier:
    print(i, article)
    i = i + 1

Sur le papier, la logique tenait debout. Mais dans la réalité de mes soirées de fatigue, ce pauvre i devenait un nid à bêtises. Un soir, j'oubliais carrément la ligne d'incrémentation, et le terminal me répétait inlassablement le même chiffre. Le lendemain, je glissais l'incrémentation au mauvais endroit dans un bloc conditionnel. L'écran du portable posé entre la tasse d'infusion refroidie et le cahier à spirale ouvert sur une page de gribouillis d'index, je restais parfois dix minutes à me demander pourquoi mon compte s'arrêtait en cours de route.

Cahier de notes manuscrites à spirale et clavier d'ordinateur portable lors d'une session de code

Le pire est arrivé quand j'ai décalé l'indentation sans faire attention. Le terminal qui m'affiche '0. Pain' puis plante avec une erreur d'indentation parce que j'avais mal aligné mon compteur manuel, c'est le genre de micro-échec ridicule qui donne envie de fermer le capot de la machine et d'aller dormir direct.

Le piège du range(len()) et la numérotation des machines

Quand on demande de l'aide sur les forums ou qu'on regarde de vieux cours, la première béquille qu'on vous tend ressemble à une formule mathématique un peu raide : fabriquer une série de chiffres à partir de la longueur de la liste. J'en avais parlé en détail dans mes notes pour bien utiliser la fonction range Python dans ses boucles for, mais à l'époque, j'avais l'impression de reculer au lieu d'avancer.

Écrire for i in range(len(panier)): obligeait ensuite à aller repêcher l'article avec panier[i]. C'était lourd. C'était surtout l'inverse de ce qui m'avait plu dans le langage Python au départ. Au lieu de manipuler directement mes objets, je me remettais à manipuler des tiroirs numérotés pour deviner ce qu'il y avait dedans. Et puis, il y avait ce décalage perpétuel : en programmation, l'indice de départ par défaut dans Python est 0. Pour une machine, c'est logique ; pour un humain qui rédige une liste de tâches, voir sa première corvée étiquetée numéro zéro donne l'impression désagréable d'un brouillon inachevé.

Je passais donc mon temps à écrire des print(i + 1, panier[i]), en priant pour ne pas mélanger les parenthèses ou écraser par mégarde un nom de variable réservé.

Le soulagement enumerate() et son petit secret bien pratique

Et puis, au détour d'un exercice d'atelier, le formateur a tapé un mot que je n'avais jamais vu : enumerate(). Pas de variable extérieure, pas de calcul de longueur, pas d'incrément manuel caché au fond du bloc de code.

panier = ['Pain', 'Tisane', 'Poireaux']

for index, article in enumerate(panier):
    print(index, article)

La première fois que j'ai exécuté ces trois lignes, j'ai cru à un tour de magie. La fonction prend notre liste et fabrique elle-même deux sorties à chaque tour : la position, puis l'élément. Elle distribue les deux en même temps, comme un serveur qui poserait l'assiette et le numéro de commande d'un seul geste sur la table.

Sous le capot, comme j'ai fini par le comprendre un peu plus tard en explorant comment comprendre la différence entre liste et tuple en python simplement, enumerate renvoie en réalité un petit couple de valeurs à chaque étape, qu'on sépare directement dans l'en-tête du for. On appelle cela un déballage, mais pour moi c'était surtout la disparition immédiate de trois lignes de code parasites.

Mieux encore : la fonction embarque une option que j'ai ignorée pendant des semaines avant de la remarquer dans la documentation officielle. On peut lui donner la valeur de départ personnalisée pour un affichage humain avec l'argument start=1 :

for numero, article in enumerate(panier, start=1):
    print(f"{numero}. {article}")

Fini les i + 1 bricolés dans la chaîne de texte. Mon affichage démarre à 1, se termine pile au bon endroit, et le code reste aussi limpide qu'une recette de cuisine.

Tasse d'infusion posée près d'un ordinateur affichant un script Python tard le soir

Quand vouloir un index cache en réalité une mauvaise habitude

Au début de cet automne, avec quelques mois de recul et des dizaines de petits scripts bricolés le soir, j'ai pourtant commencé à voir cette fonction sous un autre jour. Un ami qui code depuis longtemps m'avait vue tapoter une boucle sur la nappe cirée et m'avait glissé une remarque un peu cryptique : « Si tu as absolument besoin de l'index dans ta boucle, demande-toi d'abord si tu n'es pas en train de forcer Python à penser comme un vieux tableur. »

Sur le coup, un peu vexée, j'ai rangé ma machine. Mais en reprenant mes anciens exercices, j'ai compris ce qu'il voulait dire. Bien souvent, j'utilisais enumerate pour une mauvaise raison : j'avais deux listes distinctes — disons une liste de produits et une liste de prix — et je me servais de l'index fourni par enumerate pour aller chercher le prix correspondant dans la seconde liste, avec un truc du genre prix[index].

C'est exactement là que le réflexe devient maladroit. En cherchant à gérer l'index à tout prix, on passe à côté d'outils bien plus adaptés comme zip(), qui permet d'attacher deux listes ensemble comme les deux rangées de dents d'une fermeture éclair, sans jamais avoir besoin de savoir à quel numéro de ligne on se trouve. Parfois même, une simple transformation directe de la liste règle l'affaire sans aucune boucle apparente.

Vouloir récupérer la position d'un élément n'est pas interdit, bien sûr, mais c'est souvent le symptôme qu'on découpe son problème avec des réflexes de débutant un peu rigides. Depuis cette remarque, je ne dégaine plus enumerate machinalement : je ne l'appelle que lorsque le numéro a une vraie valeur pour la personne qui lit la sortie de mon terminal, pas pour faire de la mécanique interne entre deux listes bancales.

Ce que je retiens de ces soirées d'essais

Mes boucles d'aujourd'hui n'ont plus grand-chose à voir avec mes premiers brouillons pleins de compteurs bancals. La fonction enumerate() a représenté l'un de mes premiers vrais soulagements dans mon parcours d'apprentissage autonome. Elle m'a épargné des dizaines de plantages d'indentation et m'a appris à laisser le langage faire ce pour quoi il est doué, plutôt que d'essayer de tout surveiller au doigt et à l'œil.

Tout n'est pas devenu limpide d'un coup pour autant. Il m'arrive encore de passer une demi-heure sur des absurdités, comme lors de mes essais pour utiliser la méthode split en python en soirée où j'avais confondu des espaces et des virgules dans une chaîne mal découpée.

Mais au moins, sur la question des boucles numérotées, l'angoisse du décalage d'index est restée au printemps. Il me reste encore à comprendre pourquoi certaines fonctions natives refusent de m'afficher leur contenu directement sans passer par un list() autour, mais pour ce soir, le cahier est refermé et l'infusion est bue.

Articles connexes