Devriez-vous toujours privilégier xrange () plutôt que range ()?

460

Pourquoi ou pourquoi pas?

mbac32768
la source
36
Quelqu'un peut-il décrire brièvement la différence entre les 2 pour nous les gars non-python? Peut-être que quelque chose comme "xrange () fait tout ce que range () fait, mais prend également en charge X, Y et Z"
Outlaw Programmer
87
range (n) crée une liste contenant tous les entiers 0..n-1. C'est un problème si vous atteignez une plage (1000000), car vous vous retrouverez avec une liste> 4 Mo. xrange gère cela en renvoyant un objet qui prétend être une liste, mais calcule simplement le nombre nécessaire à partir de l'index demandé et le renvoie.
Brian
4
Voir stackoverflow.com/questions/94935
James McMahon
4
Fondamentalement, alors que range(1000)est un list, xrange(1000)est un objet qui agit comme un generator(bien qu'il n'en soit certainement pas un). Aussi, xrangec'est plus rapide. Vous pouvez import timeit from timeitet ensuite créer une méthode qui a juste for i in xrange: passet une autre pour range, puis faire timeit(method1)et timeit(method2)et, et voilà, xrange est presque deux fois plus rapide parfois (c'est quand vous n'avez pas besoin d'une liste). (Pour moi, pour i in xrange(1000):passvs pour a i in range(1000):passpris 13.316725969314575vs 21.190124988555908secondes respectivement - c'est beaucoup.)
dylnmc
Un autre test de performance donne xrange(100)20% plus rapide que range(100).
Evgeni Sergeev

Réponses:

443

Pour les performances, en particulier lorsque vous itérez sur une large plage, xrange()c'est généralement mieux. Cependant, il existe encore quelques cas pour lesquels vous pourriez préférer range():

  • En python 3, range()fait ce qui xrange()existait et xrange()n'existe pas. Si vous souhaitez écrire du code qui s'exécutera sur Python 2 et Python 3, vous ne pouvez pas utiliser xrange().

  • range()peut en fait être plus rapide dans certains cas - par exemple. si itération sur la même séquence plusieurs fois. xrange()doit reconstruire l'objet entier à chaque fois, mais range()aura de vrais objets entiers. (Cependant, il fonctionnera toujours moins bien en termes de mémoire)

  • xrange()n'est pas utilisable dans tous les cas où une vraie liste est nécessaire. Par exemple, il ne prend pas en charge les tranches ou les méthodes de liste.

[Modifier] Il y a quelques articles mentionnant comment range()sera mis à niveau par l'outil 2to3. Pour mémoire, voici le résultat de l'exécution de l'outil sur certains exemples d'utilisation de range()etxrange()

RefactoringTool: Skipping implicit fixer: buffer
RefactoringTool: Skipping implicit fixer: idioms
RefactoringTool: Skipping implicit fixer: ws_comma
--- range_test.py (original)
+++ range_test.py (refactored)
@@ -1,7 +1,7 @@

 for x in range(20):
-    a=range(20)
+    a=list(range(20))
     b=list(range(20))
     c=[x for x in range(20)]
     d=(x for x in range(20))
-    e=xrange(20)
+    e=range(20)

Comme vous pouvez le voir, lorsqu'il est utilisé dans une boucle for ou compréhension, ou lorsqu'il est déjà encapsulé avec list (), la plage reste inchangée.

Brian
la source
5
Que voulez-vous dire par "la gamme deviendra un itérateur"? Cela ne devrait-il pas être "générateur"?
Michael Mior
4
Non. Le générateur fait référence à un type d'itérateur spécifique, et le nouveau rangen'est pas un itérateur de toute façon.
user2357112 prend en charge Monica
Votre deuxième puce n'a pas vraiment de sens. Vous dites que vous ne pouvez pas utiliser un objet plusieurs fois; sûr que vous pouvez! essayez xr = xrange(1,11)alors sur la ligne suivante for i in xr: print " ".join(format(i*j,"3d") for j in xr)et le tour est joué! Vous avez vos horaires jusqu'à dix. Cela fonctionne exactement comme r = range(1,11)et for i in r: print " ".join(format(i*j,"3d") for j in r)... tout est un objet en Python2. Je pense que ce que vous vouliez dire, c'est que vous pouvez faire une compréhension basée sur un index (si cela a du sens) mieux qu'avec rangeplutôt que xrange. La portée est très rare, je pense
dylnmc
(Pas assez de place) Bien, je ne pense que rangepeut être pratique si vous souhaitez utiliser un listdans une boucle, puis modifier certains indices en fonction de certaines conditions ou choses ajouter à cette liste, puis rangeest certainement mieux. Cependant, il xrangeest tout simplement plus rapide et utilise moins de mémoire, donc pour la majorité des applications en boucle, il semble être le meilleur. Il y a des cas - pour en revenir à la question du questionneur - rarement mais existants, où ce rangeserait mieux. Peut-être pas aussi rarement que je le pense, mais j'utilise certainement xrange95% du temps.
dylnmc
129

Non, ils ont tous deux leur utilité:

À utiliser xrange()lors de l'itération, car cela économise de la mémoire. Dire:

for x in xrange(1, one_zillion):

plutôt que:

for x in range(1, one_zillion):

D'un autre côté, utilisez range()si vous voulez réellement une liste de nombres.

multiples_of_seven = range(7,100,7)
print "Multiples of seven < 100: ", multiples_of_seven
Dan Lenski
la source
42

Vous privilégiez range()plus xrange()que lorsque vous avez besoin d' une liste réelle. Par exemple, lorsque vous souhaitez modifier la liste renvoyée par range(), ou lorsque vous souhaitez la découper. Pour l'itération ou même une indexation normale, xrange()cela fonctionnera bien (et généralement beaucoup plus efficacement). Il y a un point où range()c'est un peu plus rapide que xrange()pour de très petites listes, mais selon votre matériel et divers autres détails, le seuil de rentabilité peut être dû à une longueur de 1 ou 2; pas de quoi s'inquiéter. Je préfère xrange().

Thomas Wouters
la source
30

Une autre différence est que xrange () ne peut pas prendre en charge des nombres plus grands que les entiers C, donc si vous voulez avoir une plage en utilisant le support de grand nombre intégré à python, vous devez utiliser range ().

Python 2.7.3 (default, Jul 13 2012, 22:29:01) 
[GCC 4.7.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> range(123456787676676767676676,123456787676676767676679)
[123456787676676767676676L, 123456787676676767676677L, 123456787676676767676678L]
>>> xrange(123456787676676767676676,123456787676676767676679)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: Python int too large to convert to C long

Python 3 n'a pas ce problème:

Python 3.2.3 (default, Jul 14 2012, 01:01:48) 
[GCC 4.7.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> range(123456787676676767676676,123456787676676767676679)
range(123456787676676767676676, 123456787676676767676679)
Brian Minton
la source
13

xrange()est plus efficace car au lieu de générer une liste d'objets, il ne génère qu'un objet à la fois. Au lieu de 100 entiers, et de tous leurs frais généraux, et de la liste pour les mettre, vous n'avez qu'un seul entier à la fois. Génération plus rapide, meilleure utilisation de la mémoire, code plus efficace.

À moins d'avoir spécifiquement besoin d'une liste pour quelque chose, je préfère toujours xrange()

Dan Udey
la source
8

range () renvoie une liste, xrange () retourne un objet xrange.

xrange () est un peu plus rapide et un peu plus efficace en mémoire. Mais le gain n'est pas très important.

La mémoire supplémentaire utilisée par une liste n'est bien sûr pas seulement gaspillée, les listes ont plus de fonctionnalités (tranche, répétition, insertion, ...). Les différences exactes peuvent être trouvées dans la documentation . Il n'y a pas de règle stricte, utilisez ce qui est nécessaire.

Python 3.0 est encore en développement, mais la plage IIRC () sera très similaire à xrange () de 2.X et list (range ()) peut être utilisé pour générer des listes.

jschultz
la source
5

Je voudrais juste dire qu'il n'est VRAIMENT pas si difficile d'obtenir un objet xrange avec des fonctionnalités de tranche et d'indexation. J'ai écrit du code qui fonctionne assez bien et qui est aussi rapide que xrange quand ça compte (itérations).

from __future__ import division

def read_xrange(xrange_object):
    # returns the xrange object's start, stop, and step
    start = xrange_object[0]
    if len(xrange_object) > 1:
       step = xrange_object[1] - xrange_object[0]
    else:
        step = 1
    stop = xrange_object[-1] + step
    return start, stop, step

class Xrange(object):
    ''' creates an xrange-like object that supports slicing and indexing.
    ex: a = Xrange(20)
    a.index(10)
    will work

    Also a[:5]
    will return another Xrange object with the specified attributes

    Also allows for the conversion from an existing xrange object
    '''
    def __init__(self, *inputs):
        # allow inputs of xrange objects
        if len(inputs) == 1:
            test, = inputs
            if type(test) == xrange:
                self.xrange = test
                self.start, self.stop, self.step = read_xrange(test)
                return

        # or create one from start, stop, step
        self.start, self.step = 0, None
        if len(inputs) == 1:
            self.stop, = inputs
        elif len(inputs) == 2:
            self.start, self.stop = inputs
        elif len(inputs) == 3:
            self.start, self.stop, self.step = inputs
        else:
            raise ValueError(inputs)

        self.xrange = xrange(self.start, self.stop, self.step)

    def __iter__(self):
        return iter(self.xrange)

    def __getitem__(self, item):
        if type(item) is int:
            if item < 0:
                item += len(self)

            return self.xrange[item]

        if type(item) is slice:
            # get the indexes, and then convert to the number
            start, stop, step = item.start, item.stop, item.step
            start = start if start != None else 0 # convert start = None to start = 0
            if start < 0:
                start += start
            start = self[start]
            if start < 0: raise IndexError(item)
            step = (self.step if self.step != None else 1) * (step if step != None else 1)
            stop = stop if stop is not None else self.xrange[-1]
            if stop < 0:
                stop += stop

            stop = self[stop]
            stop = stop

            if stop > self.stop:
                raise IndexError
            if start < self.start:
                raise IndexError
            return Xrange(start, stop, step)

    def index(self, value):
        error = ValueError('object.index({0}): {0} not in object'.format(value))
        index = (value - self.start)/self.step
        if index % 1 != 0:
            raise error
        index = int(index)


        try:
            self.xrange[index]
        except (IndexError, TypeError):
            raise error
        return index

    def __len__(self):
        return len(self.xrange)

Honnêtement, je pense que tout le problème est un peu idiot et xrange devrait faire tout cela de toute façon ...

Garrett Berg
la source
Ouais d'accord; à partir d'une technologie totalement différente, vérifiez le travail sur lodash pour le rendre paresseux: github.com/lodash/lodash/issues/274 . Le tranchage, etc. devrait toujours être aussi paresseux que possible et sinon, réifier ensuite.
Rob Grant
4

Un bon exemple donné dans le livre: Practical Python By Magnus Lie Hetland

>>> zip(range(5), xrange(100000000))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

Je ne recommanderais pas d'utiliser range au lieu de xrange dans l'exemple précédent - bien que seuls les cinq premiers nombres soient nécessaires, range calcule tous les nombres, ce qui peut prendre beaucoup de temps. Avec xrange, ce n'est pas un problème car il ne calcule que les nombres nécessaires.

Oui, j'ai lu la réponse de @ Brian: En python 3, range () est de toute façon un générateur et xrange () n'existe pas.

Grijesh Chauhan
la source
3

Allez avec gamme pour ces raisons:

1) xrange disparaîtra dans les nouvelles versions de Python. Cela vous donne une compatibilité future facile.

2) la gamme prendra les efficacités associées à xrange.

torial
la source
13
Ne fais pas ça. xrange () disparaîtra, mais beaucoup d'autres choses aussi. L'outil que vous utiliserez pour traduire votre code Python 2.x en code Python 3.x traduira automatiquement xrange () en range (), mais range () sera traduit dans la liste moins efficace (range ()).
Thomas Wouters, le
10
Thomas: C'est en fait un peu plus intelligent que ça. Il traduira range () dans les situations où il n'a clairement pas besoin d'une vraie liste (par exemple dans une boucle for, ou de compréhension) en simplement range (). Seuls les cas où il est assigné à une variable ou utilisé directement doivent être encapsulés avec list ()
Brian
2

D'accord, tout le monde ici a une opinion différente sur les compromis et les avantages de xrange par rapport à la plage. Ils sont généralement corrects, xrange est un itérateur, et la plage s'étoffe et crée une liste réelle. Pour la majorité des cas, vous ne remarquerez pas vraiment de différence entre les deux. (Vous pouvez utiliser une carte avec plage mais pas avec xrange, mais elle utilise plus de mémoire.)

Ce que je pense que vous voulez entendre, c'est que le choix préféré est xrange. Étant donné que la plage dans Python 3 est un itérateur, l'outil de conversion de code 2to3 convertira correctement toutes les utilisations de xrange en plage et affichera une erreur ou un avertissement pour les utilisations de la plage. Si vous voulez être sûr de convertir facilement votre code à l'avenir, vous n'utiliserez que xrange et listez (xrange) lorsque vous êtes sûr de vouloir une liste. J'ai appris cela lors du sprint CPython à PyCon cette année (2008) à Chicago.

Douglas Mayle
la source
8
Ce n'est pas vrai. Le code comme "pour x dans la plage (20)" sera laissé comme plage, et le code comme "x = plage (20)" sera converti en "x = liste (plage (20))" - aucune erreur. De plus, si vous voulez écrire du code qui s'exécutera sous 2.6 et 3.0, range () est votre seule option sans ajouter de fonctions de compatibilité.
Brian
2
  • range(): range(1, 10)renvoie une liste de 1 à 10 chiffres et conserve toute la liste en mémoire.
  • xrange(): Comme range(), mais au lieu de renvoyer une liste, renvoie un objet qui génère les nombres dans la plage à la demande. Pour le bouclage, c'est légèrement plus rapide range()et plus efficace en mémoire. xrange()objet comme un itérateur et génère les nombres à la demande (Évaluation paresseuse).
In [1]: range(1,10)
Out[1]: [1, 2, 3, 4, 5, 6, 7, 8, 9]

In [2]: xrange(10)
Out[2]: xrange(10)

In [3]: print xrange.__doc__
Out[3]: xrange([start,] stop[, step]) -> xrange object

range()fait la même chose que xrange()dans Python 3 et il n’existe pas de terme xrange()dans Python 3. range()peut en fait être plus rapide dans certains scénarios si vous répétez plusieurs fois la même séquence. xrange()doit reconstruire l'objet entier à chaque fois, mais range()aura de vrais objets entiers.

Tushar.PUCSD
la source
2

Bien qu'il xrangesoit plus rapide que rangedans la plupart des cas, la différence de performances est assez minime. Le petit programme ci-dessous compare l'itération sur un rangeet un xrange:

import timeit
# Try various list sizes.
for list_len in [1, 10, 100, 1000, 10000, 100000, 1000000]:
  # Time doing a range and an xrange.
  rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n'%list_len, number=1000)
  xrtime = timeit.timeit('a=0;\nfor n in xrange(%d): a += n'%list_len, number=1000)
  # Print the result
  print "Loop list of len %d: range=%.4f, xrange=%.4f"%(list_len, rtime, xrtime)

Les résultats ci-dessous montrent que xrangec'est en effet plus rapide, mais pas suffisant pour transpirer.

Loop list of len 1: range=0.0003, xrange=0.0003
Loop list of len 10: range=0.0013, xrange=0.0011
Loop list of len 100: range=0.0068, xrange=0.0034
Loop list of len 1000: range=0.0609, xrange=0.0438
Loop list of len 10000: range=0.5527, xrange=0.5266
Loop list of len 100000: range=10.1666, xrange=7.8481
Loop list of len 1000000: range=168.3425, xrange=155.8719

Donc, par tous les moyens xrange, mais à moins que vous ne soyez sur un matériel contraint, ne vous en faites pas trop.

speedplane
la source
Votre list_lenn'est pas utilisé et vous n'exécutez donc ce code que pour les listes de longueur 100.
Mark
Je recommande de modifier la longueur de la liste:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Mark
Wow, cela s'annonce comme une bonne semaine, merci, fixe. Pourtant, ce n'est pas une différence majeure.
speedplane