Skip to content
Thèmes
Python
Python Enumerate: Loop with Index the Right Way

Python Enumerate : Boucler avec index de la bonne manière

Updated on

Le suivi des indices lors du parcours de séquences Python est un problème courant auquel les développeurs sont confrontés quotidiennement. L'approche typique d'utiliser range(len(list)) fonctionne, mais elle crée un code verbeux qui obscurcit votre intention et introduit des opportunités inutiles d'erreurs de décalage. Lorsque vous avez besoin à la fois de l'élément et de sa position, la gestion manuelle d'une variable de compteur ajoute une charge cognitive et rend votre code plus difficile à lire.

La fonction intégrée enumerate() de Python résout ce problème de manière élégante. Elle enveloppe n'importe quel itérable et renvoie des paires d'indices et de valeurs, éliminant la gestion manuelle des compteurs tout en rendant votre code plus pythonique et lisible. Ce guide vous montre comment utiliser enumerate() efficacement, des modèles de base aux techniques avancées sur lesquelles s'appuient les développeurs Python professionnels.

📚

Qu'est-ce que Python Enumerate ?

La fonction enumerate() est un outil Python intégré qui ajoute un compteur à un itérable et le renvoie sous forme d'objet enumerate. Cet objet produit des paires de tuples (index, valeur) lorsque vous itérez à travers lui.

fruits = ['apple', 'banana', 'cherry']
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")
 
# Output:
# 0: apple
# 1: banana
# 2: cherry

La signature de la fonction est enumerate(iterable, start=0), où iterable est n'importe quelle séquence ou itérateur, et start est la valeur d'index de départ optionnelle.

Syntaxe et utilisation de base d'Enumerate

Boucle simple avec index

Au lieu d'utiliser le modèle range(len()), enumerate() fournit un accès direct à la fois à l'index et à la valeur :

# Without enumerate (less Pythonic)
colors = ['red', 'green', 'blue']
for i in range(len(colors)):
    print(f"Color {i} is {colors[i]}")
 
# With enumerate (Pythonic)
for i, color in enumerate(colors):
    print(f"Color {i} is {color}")

Paramètre de départ personnalisé

Le paramètre start vous permet de commencer à compter à partir de n'importe quel nombre :

tasks = ['Review code', 'Write tests', 'Deploy']
for num, task in enumerate(tasks, start=1):
    print(f"Task #{num}: {task}")
 
# Output:
# Task #1: Review code
# Task #2: Write tests
# Task #3: Deploy

Ceci est particulièrement utile lors de l'affichage de listes numérotées aux utilisateurs, où l'indexation basée sur 1 est plus naturelle que celle basée sur 0.

Enumerate avec différents itérables

Listes et tuples

enumerate() fonctionne parfaitement avec les listes et les tuples :

coordinates = [(10, 20), (30, 40), (50, 60)]
for idx, (x, y) in enumerate(coordinates):
    print(f"Point {idx}: x={x}, y={y}")

Chaînes de caractères

Les chaînes sont itérables, donc enumerate() les traite caractère par caractère :

word = "Python"
for position, char in enumerate(word):
    print(f"Character at position {position}: {char}")
 
# Output:
# Character at position 0: P
# Character at position 1: y
# Character at position 2: t
# ...

Dictionnaires

Lors de l'énumération de dictionnaires, vous itérez sur les clés par défaut :

config = {'host': 'localhost', 'port': 8080, 'debug': True}
 
# Enumerate keys
for i, key in enumerate(config):
    print(f"{i}: {key}")
 
# Enumerate key-value pairs
for i, (key, value) in enumerate(config.items()):
    print(f"{i}: {key} = {value}")

Objets fichier

enumerate() est particulièrement utile lors du traitement de fichiers ligne par ligne :

with open('data.txt', 'r') as file:
    for line_num, line in enumerate(file, start=1):
        if 'ERROR' in line:
            print(f"Error found on line {line_num}: {line.strip()}")

Comparaison : Enumerate vs autres méthodes de suivi d'index

MéthodeLisibilitéPerformanceMémoireCas d'usage
enumerate(list)ÉlevéeRapideFaible (itérateur paresseux)Quand vous avez besoin d'index et de valeur
range(len(list))FaibleRapideFaibleCode hérité (à éviter)
zip(range(), list)MoyenneRapideFaibleLors de la combinaison de plusieurs itérables
Compteur manuelFaibleRapideFaibleNe jamais utiliser (sujet aux erreurs)
data = ['a', 'b', 'c']
 
# Method 1: enumerate (recommandé)
for i, item in enumerate(data):
    print(i, item)
 
# Method 2: range(len()) (pas pythonique)
for i in range(len(data)):
    print(i, data[i])
 
# Method 3: zip with range (trop complexe)
for i, item in zip(range(len(data)), data):
    print(i, item)
 
# Method 4: manual counter (sujet aux erreurs)
counter = 0
for item in data:
    print(counter, item)
    counter += 1

L'approche enumerate() l'emporte en lisibilité et en style pythonique tout en maintenant d'excellentes performances.

Modèles avancés d'Enumerate

Enumerate avec les compréhensions de listes

Combinez enumerate() avec les compréhensions de listes pour des transformations concises :

numbers = [10, 20, 30, 40]
 
# Add index to each element
indexed = [(i, n) for i, n in enumerate(numbers)]
# Result: [(0, 10), (1, 20), (2, 30), (3, 40)]
 
# Filter based on index
even_positions = [n for i, n in enumerate(numbers) if i % 2 == 0]
# Result: [10, 30]
 
# Transform with index
multiplied = [n * i for i, n in enumerate(numbers, start=1)]
# Result: [10, 40, 90, 160]

Enumerate avec Zip

Combinez enumerate() et zip() pour itérer sur plusieurs séquences avec des indices :

names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]
 
for rank, (name, score) in enumerate(zip(names, scores), start=1):
    print(f"#{rank}: {name} scored {score}")
 
# Output:
# #1: Alice scored 85
# #2: Bob scored 92
# #3: Charlie scored 78

Enumerate en ordre inverse

Pour énumérer en ordre inverse, combinez enumerate() avec reversed() :

items = ['first', 'second', 'third']
 
# Reverse the items, but indices still go 0, 1, 2
for i, item in enumerate(reversed(items)):
    print(f"{i}: {item}")
 
# Output:
# 0: third
# 1: second
# 2: first
 
# If you want descending indices, calculate them manually
length = len(items)
for i, item in enumerate(reversed(items)):
    actual_index = length - 1 - i
    print(f"{actual_index}: {item}")
 
# Output:
# 2: third
# 1: second
# 0: first

Enumerate imbriqué

Utilisez des appels enumerate() imbriqués pour les données multidimensionnelles :

matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]
 
for row_idx, row in enumerate(matrix):
    for col_idx, value in enumerate(row):
        print(f"matrix[{row_idx}][{col_idx}] = {value}")

Ce modèle est précieux pour les algorithmes basés sur une grille, les plateaux de jeu ou toute structure imbriquée où vous avez besoin de coordonnées.

Performance et efficacité mémoire

La fonction enumerate() renvoie un itérateur, pas une liste. Cela signifie qu'elle génère des paires index-valeur à la demande plutôt que de les créer toutes d'avance en mémoire.

# enumerate returns an iterator
large_list = range(1_000_000)
enum_obj = enumerate(large_list)
print(type(enum_obj))  # <class 'enumerate'>
 
# Memory efficient - doesn't create a million tuples at once
for i, value in enum_obj:
    if i > 5:
        break
    print(i, value)

Comparaison de benchmark

import timeit
 
data = list(range(10_000))
 
# Time enumerate
time_enum = timeit.timeit(
    'for i, x in enumerate(data): pass',
    globals={'data': data},
    number=1000
)
 
# Time range(len())
time_range = timeit.timeit(
    'for i in range(len(data)): x = data[i]',
    globals={'data': data},
    number=1000
)
 
print(f"enumerate: {time_enum:.4f}s")
print(f"range(len): {time_range:.4f}s")
# enumerate is typically 10-20% faster due to fewer lookups

La différence de performance provient du fait que enumerate() évite les opérations d'indexation répétées. Lors de l'utilisation de data[i], Python doit effectuer une recherche pour chaque élément, alors que enumerate() produit directement les valeurs.

Erreurs courantes à éviter

Erreur 1 : Modifier la longueur de la séquence pendant l'énumération

# Wrong - causes unexpected behavior
items = [1, 2, 3, 4, 5]
for i, item in enumerate(items):
    if item % 2 == 0:
        items.remove(item)  # Modifies list during iteration
 
# Correct - collect indices first
items = [1, 2, 3, 4, 5]
to_remove = [i for i, item in enumerate(items) if item % 2 == 0]
for i in reversed(to_remove):
    items.pop(i)

Erreur 2 : Déballage sans tuple

# Wrong - tries to unpack the enumerate object itself
for item in enumerate(['a', 'b']):
    print(item)  # Prints: (0, 'a'), (1, 'b')
 
# Correct - unpack each tuple
for i, item in enumerate(['a', 'b']):
    print(i, item)

Erreur 3 : Convertir en liste inutilement

# Wrong - wastes memory
for i, item in list(enumerate(huge_dataset)):
    process(i, item)
 
# Correct - keep it as iterator
for i, item in enumerate(huge_dataset):
    process(i, item)

Erreur 4 : Utiliser Enumerate quand vous n'avez pas besoin de l'index

# Wrong - unnecessary complexity
for i, item in enumerate(items):
    print(item)  # Never uses i
 
# Correct - simple iteration
for item in items:
    print(item)

Erreur 5 : Ignorer le paramètre Start pour l'affichage utilisateur

# Wrong - users see 0-based indexing
results = ['first', 'second', 'third']
for i, result in enumerate(results):
    print(f"{i}. {result}")  # 0. first, 1. second...
 
# Correct - users see natural numbering
for i, result in enumerate(results, start=1):
    print(f"{i}. {result}")  # 1. first, 2. second...

Sous le capot : Comment fonctionne Enumerate

Le enumerate() de Python est implémenté comme une classe d'itérateur. Voici une version simplifiée de son fonctionnement interne :

class Enumerate:
    def __init__(self, iterable, start=0):
        self.iterable = iter(iterable)
        self.count = start
 
    def __iter__(self):
        return self
 
    def __next__(self):
        value = next(self.iterable)
        result = (self.count, value)
        self.count += 1
        return result
 
# Using our implementation
items = ['x', 'y', 'z']
for i, item in Enumerate(items):
    print(i, item)

Cette implémentation révèle pourquoi enumerate() est efficace en mémoire. Elle ne pré-calcule pas toutes les paires index-valeur ; au lieu de cela, elle maintient un compteur et génère des paires à chaque itération.

L'implémentation réelle de CPython est optimisée en C pour des performances maximales, mais suit le même protocole d'itérateur.

Cas d'usage du monde réel

Traitement de fichiers CSV avec numéros de ligne

import csv
 
with open('sales.csv', 'r') as file:
    reader = csv.DictReader(file)
    for row_num, row in enumerate(reader, start=2):  # start=2 accounts for header
        try:
            amount = float(row['amount'])
            if amount < 0:
                print(f"Warning: Negative amount on row {row_num}")
        except ValueError:
            print(f"Error: Invalid amount on row {row_num}: {row['amount']}")

Construction de listes ordonnées HTML

def create_html_list(items):
    html = "<ol>\n"
    for i, item in enumerate(items, start=1):
        html += f'  <li id="item-{i}">{item}</li>\n'
    html += "</ol>"
    return html
 
tasks = ["Write code", "Review PR", "Deploy"]
print(create_html_list(tasks))

Suivi de la progression dans le traitement des données

def process_dataset(data, batch_size=100):
    total = len(data)
    for i, record in enumerate(data):
        process_record(record)
 
        # Show progress every batch_size items
        if (i + 1) % batch_size == 0:
            progress = (i + 1) / total * 100
            print(f"Progress: {progress:.1f}% ({i + 1}/{total})")

Lors du traitement de données dans les notebooks Jupyter, le suivi des indices devient encore plus précieux pour le débogage. Des outils comme RunCell (opens in a new tab) aident les data scientists à déboguer les boucles énumérées en fournissant une analyse alimentée par l'IA de vos modèles d'itération et des états de variables à chaque étape.

Trouver toutes les occurrences dans le texte

def find_all_positions(text, substring):
    positions = [i for i, char in enumerate(text) if text[i:i+len(substring)] == substring]
    return positions
 
text = "Python is powerful. Python is popular. Python is everywhere."
positions = find_all_positions(text, "Python")
print(f"'Python' found at positions: {positions}")
# Output: 'Python' found at positions: [0, 20, 40]

FAQ

Que fait enumerate en Python ?

La fonction enumerate() ajoute un compteur à n'importe quel objet itérable et renvoie un objet enumerate qui produit des paires de tuples (index, valeur). Elle élimine le besoin de suivre manuellement les indices lors du parcours de séquences, rendant le code plus lisible et moins sujet aux erreurs. La fonction prend un paramètre start optionnel pour commencer à compter à partir de n'importe quel nombre au lieu de la valeur par défaut 0.

En quoi enumerate diffère-t-il de range ?

La fonction enumerate() fonctionne avec n'importe quel itérable et renvoie à la fois l'index et la valeur réelle de l'élément, tandis que range() ne génère que des nombres que vous devez utiliser pour indexer la séquence manuellement. L'utilisation de enumerate(items) est plus pythonique et lisible que range(len(items)) car elle évite les opérations d'indexation redondantes et exprime clairement l'intention d'itérer avec des indices.

Peut-on utiliser enumerate avec des dictionnaires ?

Oui, enumerate() fonctionne avec les dictionnaires. Lorsque vous énumérez un dictionnaire directement, il opère sur les clés. Pour énumérer les paires clé-valeur, utilisez enumerate(dict.items()) qui vous donne un index plus le tuple (clé, valeur). Ce modèle est utile lorsque vous devez suivre la position des entrées de dictionnaire pendant l'itération.

Enumerate est-il plus rapide que l'utilisation de range(len()) ?

Oui, enumerate() est généralement 10 à 20 % plus rapide que range(len()) car il évite les opérations de recherche d'index répétées. Lorsque vous utilisez data[i] dans une boucle, Python effectue une recherche pour chaque élément, alors que enumerate() produit directement les valeurs de l'itérateur. La différence de performance devient plus notable avec des ensembles de données plus grands et est plus significative lorsqu'elle est combinée avec la lisibilité supérieure d'enumerate.

Enumerate crée-t-il une liste en mémoire ?

Non, enumerate() renvoie un itérateur paresseux qui génère des paires index-valeur à la demande plutôt que de les créer toutes à la fois en mémoire. Cela le rend efficace en mémoire même avec de très grands ensembles de données. Chaque tuple n'est créé que lorsqu'il est demandé pendant l'itération, donc énumérer une liste d'un million d'éléments ne crée pas un million de tuples d'avance. Si vous avez besoin d'une liste réelle de tuples, vous devez la convertir explicitement avec list(enumerate(data)).

Conclusion

La fonction enumerate() est un outil fondamental dans la boîte à outils de chaque développeur Python. Elle transforme les modèles verbeux de suivi d'index en code propre et lisible qui communique clairement l'intention. En renvoyant des itérateurs paresseux de paires index-valeur, enumerate offre à la fois performance et efficacité mémoire tout en éliminant les erreurs courantes associées à la gestion manuelle des compteurs.

Commencez à utiliser enumerate() chaque fois que vous avez besoin à la fois de la position et de la valeur dans vos boucles. Adoptez le paramètre start pour la numérotation orientée utilisateur, combinez-le avec zip() pour l'itération multi-séquence et exploitez-le dans les compréhensions de listes pour des transformations concises. Ces modèles rendront votre code Python plus pythonique, maintenable et professionnel.

📚