La fonction next() est l’un des outils les plus utiles dès qu’on travaille avec des itérateurs en Python. Elle permet d’avancer élément par élément, de récupérer une valeur initiale, de gérer proprement une séquence vide et d’écrire des traitements plus précis sur des flux de données, des générateurs ou des objets personnalisés. Dans ce guide, je vais aller droit au but: comment elle fonctionne, quand l’utiliser, comment éviter les erreurs classiques et pourquoi elle reste si pratique dans du code réel.
Les points essentiels à retenir sur next()
-
next()lit l’élément suivant d’un itérateur et fait avancer sa position interne. - Une liste, une chaîne ou un tuple sont des itérables, mais pas des itérateurs tant qu’on ne passe pas par
iter(). - Si l’itérateur est épuisé,
next()lèveStopIteration, sauf si un défaut est fourni. - Le paramètre de repli est utile quand une absence de valeur est normale, pas exceptionnelle.
- Dans un générateur, il vaut mieux utiliser
returnque leverStopIterationà la main. - En pratique,
next()sert surtout à un accès ponctuel, pas à remplacer une boucle complète.
Comment next() avance dans un itérateur
Le principe est très simple: next() appelle le mécanisme interne de l’itérateur et renvoie l’élément suivant. Le point important, c’est que l’objet n’est pas relu depuis le début à chaque appel; il avance réellement dans la séquence. C’est ce qui le rend utile pour les flux, les générateurs et les objets qui produisent leurs valeurs à la demande.
nombres = iter([10, 20, 30])
print(next(nombres)) # 10
print(next(nombres)) # 20
print(next(nombres)) # 30
print(next(nombres)) # StopIteration
Dans un for, Python fait ce travail à votre place: il crée d’abord un itérateur avec iter(), puis appelle next() en boucle jusqu’à ce que la séquence soit épuisée. C’est pour cela qu’un for reste la solution la plus lisible quand vous voulez tout parcourir, alors que next() devient intéressant dès que vous voulez garder la main sur la progression. La différence entre objet itérable et itérateur est justement ce qui évite les confusions les plus fréquentes.
Itérable, itérateur et générateur ne jouent pas le même rôle
Je vois souvent cette confusion en revue de code: une liste peut être parcourue, mais elle n’accepte pas directement next(). Il faut d’abord la transformer en itérateur. À l’inverse, un générateur est déjà un itérateur, donc on peut l’utiliser tout de suite avec next().
| Objet | Peut être parcouru avec for
|
Accepte next() directement |
Remarque |
|---|---|---|---|
| Liste | Oui | Non | Il faut appeler iter(liste) avant. |
| Itérateur | Oui | Oui | Chaque appel avance la position interne. |
| Générateur | Oui | Oui | Créé avec yield ou une expression génératrice. |
Cette distinction compte parce qu’elle explique beaucoup d’erreurs du type TypeError au moment où l’on essaie d’appeler next() sur un objet qui n’est pas encore un itérateur. Une fois ce point clarifié, la vraie question devient celle de la fin de séquence et de la manière de l’exprimer proprement.
Gérer la fin proprement avec une valeur par défaut
Quand un itérateur arrive au bout, next() lève StopIteration. Ce comportement est parfaitement normal, mais il n’est pas toujours le plus pratique si l’absence d’élément est un cas attendu. Dans ce cas, la forme next(iterateur, valeur_par_defaut) est plus confortable.
it = iter([])
print(next(it, "vide")) # vide
Cette variante évite une exception quand vous savez qu’une séquence peut être vide. Je l’utilise souvent pour récupérer une première valeur facultative, un premier élément de configuration ou un enregistrement initial dans un flux. En revanche, je reste prudent avec None comme valeur par défaut: si None est une donnée possible dans votre domaine, vous perdez l’information qui permet de distinguer “absence” et “valeur réelle”. Dans ce cas, un objet sentinelle unique est plus sûr.
sentinelle = object()
valeur = next(it, sentinelle)
if valeur is sentinelle:
print("Aucune valeur disponible")
else:
print(valeur)
Le bon réflexe est simple: si la fin du flux est normale, utilisez un défaut; si elle signale un problème, laissez StopIteration remonter. Avec cette base, on peut regarder les situations concrètes où la fonction apporte vraiment un gain de lisibilité.
Les cas où next() apporte vraiment quelque chose
Je ne conseille pas d’utiliser next() partout. En revanche, dans quelques scénarios précis, il rend le code plus direct et plus clair que l’écriture d’une boucle complète.
- Lire le premier élément d’une séquence ou d’un flux sans parcourir tout le reste.
- Sauter un en-tête dans un fichier texte ou CSV avant de traiter les lignes utiles.
- Récupérer la première correspondance d’un filtre sans construire une boucle explicite.
- Consommer un flux progressif quand on a besoin d’avancer à la demande, pas en bloc.
logs = (
ligne for ligne in journal
if ligne["niveau"] == "ERROR"
)
premiere_erreur = next(logs, None)
Dans cet exemple, la recherche s’arrête dès qu’une ligne correspond. C’est particulièrement intéressant sur des flux importants, des journaux applicatifs ou des traitements réseau, parce qu’on évite de parcourir plus de données que nécessaire. Je trouve aussi ce style plus expressif qu’un indicateur manuel qui changerait de valeur dans une boucle. Reste toutefois à connaître les pièges qui font perdre le bénéfice de cette approche.
Les erreurs que je vois le plus souvent
La plupart des problèmes autour de next() ne viennent pas de la fonction elle-même, mais d’une mauvaise compréhension de l’état de l’itérateur. Voici les erreurs que je corrige le plus souvent.
| Erreur | Conséquence | Bonne pratique |
|---|---|---|
Appeler next() sur une liste |
TypeError immédiat |
Convertir d’abord l’objet avec iter(). |
Oublier que next() consomme l’élément |
Valeur perdue ou itérateur déjà avancé | Traiter la valeur au moment de la lecture. |
Utiliser None comme défaut alors que None est une donnée valide |
Ambiguïté dans le résultat | Employer une sentinelle dédiée. |
Lever StopIteration à la main dans un générateur |
Comportement fragile, souvent converti en RuntimeError
|
Terminer le générateur avec return. |
Remplacer une boucle complète par une série d’appels à next()
|
Code plus dur à lire | Garder for pour les parcours complets. |
Le cas des générateurs mérite une attention particulière: depuis Python 3.7, lever StopIteration directement dans un générateur est une mauvaise idée, car l’exception est traitée de manière à éviter des erreurs silencieuses. Pour terminer un générateur, un simple return reste la bonne forme. Une fois ces pièges écartés, on peut passer à la création d’itérateurs plus structurés.
Créer ses propres itérateurs quand le flux est stateful
Quand un traitement doit conserver un état interne, next() devient encore plus intéressant. Un itérateur personnalisé peut, par exemple, lire des paquets réseau, consommer des événements ou produire des valeurs calculées au fil de l’eau. Dans ce cas, l’interface repose sur deux méthodes: __iter__() et __next__().
class Compteur:
def __init__(self, debut, fin):
self.courant = debut
self.fin = fin
def __iter__(self):
return self
def __next__(self):
if self.courant > self.fin:
raise StopIteration
valeur = self.courant
self.courant += 1
return valeur
compteur = Compteur(1, 3)
print(next(compteur)) # 1
print(next(compteur)) # 2
print(next(compteur)) # 3
Ce type de classe est utile quand l’état doit vivre dans l’objet lui-même, par exemple pour exposer plusieurs méthodes ou coordonner une logique plus complexe. Mais si votre besoin se résume à produire des valeurs successives, un générateur est souvent plus simple.
def compteur(debut, fin):
courant = debut
while courant <= fin:
yield courant
courant += 1
En pratique, je réserve la classe aux cas où le comportement doit être riche ou réutilisable, et je choisis le générateur dès que la logique tient en quelques lignes. La vraie question n’est pas “peut-on le faire avec next() ?”, mais “est-ce que cette forme raconte clairement l’intention du code ?”. C’est ce filtre qui permet de garder un code lisible même quand l’itération devient plus technique.
Ce que je garde en tête pour écrire du code plus lisible avec next()
Ma règle est simple: j’utilise for pour parcourir entièrement une collection, next() pour prélever une valeur précise, et next(..., default) seulement quand l’absence de donnée est un cas normal. Dès que la logique commence à nécessiter plusieurs appels successifs, je regarde souvent si un générateur, une expression génératrice ou un outil de itertools ne raconterait pas mieux la même histoire.
Autrement dit, next() n’est pas une alternative systématique à la boucle, mais un outil de précision. Bien utilisé, il rend le code plus court, plus explicite et plus robuste sur les flux; mal placé, il complique inutilement la lecture. C’est exactement pour cela qu’il mérite d’être compris au niveau du protocole d’itération, pas seulement au niveau de la syntaxe.