Un ancien script Python peut fonctionner parfaitement jusqu’au jour où xrange déclenche un NameError. Le terme python xrange renvoie à une fonction de Python 2, remplacée par range() en Python 3. Voici la différence concrète, les effets sur la mémoire et les bonnes pratiques pour adapter un code existant.
Ce qu’il faut retenir immédiatement
-
xrange()appartient à Python 2 et n’existe plus en Python 3. - En Python 3,
range()reprend le comportement économe en mémoire de l’ancienxrange(). -
range(5)produit les valeurs de 0 à 4, car la borne finale est exclue. - Utilisez
list(range(...))uniquement lorsque vous avez réellement besoin d’une liste complète. - Un pas nul provoque une erreur, tandis qu’un pas mal orienté peut produire une séquence vide.
Pourquoi xrange existait dans Python 2
Dans Python 2, range() construisait immédiatement une liste complète d’entiers. Avec range(1000000), le programme préparait donc un million de valeurs avant même de commencer la boucle. Cette approche était simple, mais elle pouvait consommer beaucoup de mémoire.
xrange() a été conçu pour éviter cette allocation. Il renvoyait un objet capable de fournir les nombres au fur et à mesure de l’itération, sans stocker toute la séquence en mémoire. C’est ce qu’on appelle une évaluation paresseuse, ou lazy evaluation.
# Python 2
for i in xrange(5):
print i
Le résultat était le même qu’avec une boucle utilisant range(), mais la gestion interne différait. Dans la pratique, xrange() était surtout utile pour les grandes boucles ou les traitements répétés. Pour quelques dizaines de valeurs, la différence restait généralement négligeable.
Ce qui a changé avec Python 3
Python 3 a supprimé xrange() et a donné à range() le comportement économe en mémoire qui caractérisait l’ancien outil. En clair, cette écriture moderne est normalement suffisante :
# Python 3
for i in range(5):
print(i)
Dans Python 3, range(5) ne renvoie pas une liste, mais un objet range. Il conserve principalement les paramètres de la séquence, comme le début, la fin et le pas, puis calcule les valeurs lorsque le programme les demande.
Je vois souvent une confusion sur ce point. range() n’est pas un générateur au sens strict, car l’objet peut être parcouru plusieurs fois et prend en charge l’indexation. Il se comporte comme une séquence numérique compacte, pas comme une liste déjà remplie.
| Version | range() |
xrange() |
|---|---|---|
| Python 2 | Crée une liste | Crée une séquence économe en mémoire |
| Python 3 | Crée un objet range économe en mémoire | N’existe pas |

Comment utiliser range correctement
La syntaxe accepte une, deux ou trois valeurs. La borne stop n’est jamais incluse, détail qui explique une grande partie des erreurs de débutant.
range(stop)
range(start, stop)
range(start, stop, step)
Une seule valeur
list(range(5))
# [0, 1, 2, 3, 4]
Avec un seul argument, Python commence à zéro et s’arrête juste avant la valeur indiquée. Ainsi, range(1) contient uniquement 0, tandis que range(5) contient cinq nombres.
Un début et une fin
list(range(2, 6))
# [2, 3, 4, 5]
Cette forme est pratique pour parcourir un intervalle précis. Je recommande de vérifier mentalement la première et la dernière valeur avant d’exécuter la boucle, surtout lorsqu’un index sert à accéder à une liste.
Un pas personnalisé
list(range(0, 10, 2))
# [0, 2, 4, 6, 8]
Le troisième argument définit le pas. Un pas négatif permet de parcourir les valeurs en sens inverse :
list(range(5, 0, -1))
# [5, 4, 3, 2, 1]
Le pas ne peut jamais être égal à zéro. range(1, 5, 0) provoque une exception ValueError, car Python ne pourrait pas progresser dans la séquence.
Comment migrer un ancien code Python 2
Dans la majorité des cas, la migration est directe. Remplacez xrange() par range(), puis adaptez la syntaxe d’affichage si le programme utilise aussi Python 2.
# Ancien code
for i in xrange(10):
print i
# Code Python 3
for i in range(10):
print(i)
La différence devient importante lorsque l’ancien programme attendait explicitement une liste. En Python 3, il faut la demander clairement :
valeurs = list(range(10))
valeurs.append(10)
Un objet range n’est pas modifiable et ne possède pas les méthodes d’une liste, comme append() ou remove(). Mon conseil est simple : gardez range() pour parcourir une séquence et utilisez list(range()) uniquement si votre traitement doit modifier, trier ou sérialiser les valeurs.
Si vous maintenez temporairement une bibliothèque compatible avec Python 2 et Python 3, une couche de compatibilité peut être nécessaire. Mais pour un nouveau projet en 2026, conserver xrange() n’apporte généralement rien et complique les tests, le déploiement et la maintenance.
Range économise de la mémoire, mais pas tous les coûts
Un objet range occupe une quantité de mémoire réduite, même lorsqu’il représente un grand intervalle. C’est son principal avantage par rapport à une liste, mais cela ne signifie pas que le traitement devient instantané.
La boucle doit toujours exécuter son contenu pour chaque valeur. Si le corps effectue une requête réseau, une opération sur disque ou un calcul complexe, remplacer une liste par range() ne supprimera pas ce coût. Le gain porte surtout sur l’allocation initiale et la mémoire utilisée.
nombres = range(10_000_000)
for nombre in nombres:
pass
Cette construction évite de créer immédiatement dix millions d’objets dans une liste. En revanche, écrire list(range(10_000_000)) annule cet avantage, puisque toutes les valeurs sont alors matérialisées en mémoire.
Autre détail utile, range() est réutilisable :
indices = range(3)
print(list(indices))
print(list(indices))
Les deux conversions fonctionnent. Cela le distingue d’un itérateur consommable une seule fois, même si, pour une simple boucle, cette nuance reste souvent invisible.
Les erreurs qui reviennent le plus souvent
Utiliser xrange en Python 3
for i in xrange(5):
print(i)
Ce code provoque NameError: name 'xrange' is not defined. La correction normale consiste à écrire range(5).
Oublier que la fin est exclue
list(range(1, 5))
# [1, 2, 3, 4]
Pour inclure 5, utilisez range(1, 6). Cette règle est cohérente avec les tranches de listes et rend les calculs d’index plus sûrs.
Choisir un pas incompatible avec la direction
list(range(5, 1, 1))
# []
La séquence veut partir de 5 vers 1, mais le pas augmente la valeur. Il faut donc utiliser -1 :
list(range(5, 1, -1))
# [5, 4, 3, 2]
Lire aussi : Valeur absolue Python - abs() vs math.fabs() : Le guide complet
Confondre range et liste
range(4) ne permet pas d’ajouter directement un élément et son affichage peut surprendre :
valeurs = range(4)
print(valeurs)
# range(0, 4)
Si le programme attend la représentation [0, 1, 2, 3], convertissez explicitement l’objet avec list(valeurs). Cette conversion doit être un choix fonctionnel, pas un réflexe automatique.
Le bon réflexe pour les projets Python actuels
Pour du code Python 3, retenez une règle pratique : remplacez xrange() par range(), puis ne créez une liste que si votre logique en a besoin. Vous obtenez ainsi une syntaxe actuelle, une consommation mémoire maîtrisée et un code plus facile à lire.
La vraie question n’est donc plus de choisir entre range et xrange. Il faut plutôt déterminer si le programme doit parcourir une séquence compacte ou manipuler une collection complète. Dans la plupart des boucles, range() est le choix naturel, fiable et largement suffisant.