313 votes

Pourquoi n'y a-t-il pas de fonction xrange en Python3?

Récemment, j'ai commencé à utiliser Python3 et son absence de xrange fait mal.

Exemple simple:

  1. Python2:

    from time import time as t
    def count():
      st = t()
      [x for x in xrange(10000000) if x%4 == 0]
      et = t()
      print et-st
    count()
  2. Python3:

    from time import time as t
    
    def xrange(x):
    
        return iter(range(x))
    
    def count():
        st = t()
        [x for x in xrange(10000000) if x%4 == 0]
        et = t()
        print (et-st)
    count()

Les résultats sont respectivement:

  1. 1.53888392448
  2. 3.215819835662842

Pourquoi? Je veux dire, pourquoi xrange a été supprimé? C'est un outil formidable pour apprendre. Pour les débutants, comme moi-même, comme nous l'avons tous été à un moment donné. Pourquoi le supprimer? Est-ce que quelqu'un peut me pointer vers le bon PEP, je ne le trouve pas.

198voto

abarnert Points 94246

Quelques mesures de performance, en utilisant timeit au lieu d'essayer de le faire manuellement avec time.

Tout d'abord, Apple 2.7.2 64-bit :

In [37]: %timeit collections.deque((x for x in xrange(10000000) if x%4 == 0), maxlen=0)
1 boucles, meilleure de 3: 1.05 s par boucle

Maintenant, python.org 3.3.0 64-bit :

In [83]: %timeit collections.deque((x for x in range(10000000) if x%4 == 0), maxlen=0)
1 boucles, meilleure de 3: 1.32 s par boucle

In [84]: %timeit collections.deque((x for x in xrange(10000000) if x%4 == 0), maxlen=0)
1 boucles, meilleure de 3: 1.31 s par boucle

In [85]: %timeit collections.deque((x for x in iter(range(10000000)) if x%4 == 0), maxlen=0) 
1 boucles, meilleure de 3: 1.33 s par boucle

Apparemment, 3.x range est vraiment un peu plus lent que 2.x xrange. Et la fonction xrange de l'OP n'a rien à voir avec cela. (Pas étonnant, car un appel unique à l'emplacement __iter__ n'est pas susceptible d'être visible parmi les 10000000 appels à ce qui se passe dans la boucle, mais quelqu'un l'a mentionné comme une possibilité.)

Mais c'est seulement 30% plus lent. Comment l'OP a-t-il obtenu 2x plus lent ? Eh bien, si je répète les mêmes tests avec Python 32 bits, j'obtiens 1,58 vs 3,12. Donc, je suppose que c'est encore un de ces cas où 3.x a été optimisé pour les performances 64 bits de manière à nuire aux 32 bits.

Mais est-ce vraiment important ? Regardez ça, avec 3.3.0 64-bit encore une fois :

In [86]: %timeit [x for x in range(10000000) if x%4 == 0]
1 boucles, meilleure de 3: 3.65 s par boucle

Donc, construire la list prend plus de deux fois plus de temps que l'itération entière.

Et quant à "consomme beaucoup plus de ressources que Python 2.6+", d'après mes tests, il semble qu'un 3.x range soit exactement de la même taille qu'un 2.x xrange — et, même s'il était 10 fois plus grand, construire la liste inutile est toujours environ 10000000 fois plus un problème que tout ce que l'itération de la plage pourrait éventuellement faire.

Et qu'en est-il d'une boucle for explicite au lieu de la boucle C à l'intérieur de deque ?

In [87]: def consume(x):
   ....:     for i in x:
   ....:         pass
In [88]: %timeit consume(x for x in range(10000000) if x%4 == 0)
1 boucles, meilleure de 3: 1.85 s par boucle

Donc, presque autant de temps est perdu dans l'instruction for que dans le travail réel d'itération de la range.

Si vous vous inquiétez d'optimiser l'itération d'un objet plage, vous regardez probablement au mauvais endroit.


Pendant ce temps, vous continuez à demander pourquoi xrange a été supprimé, peu importe combien de fois les gens vous disent la même chose, mais je vais le répéter : Il n'a pas été supprimé : il a été renommé en range, et c'est le 2.x range qui a été supprimé.

Voici quelques preuves que l'objet range 3.3 est un descendant direct de l'objet xrange 2.x (et non de la fonction range 2.x) : la source de 3.3 range et 2.7 xrange. Vous pouvez même voir l' historique des changements (lié, je crois, au changement qui a remplacé la dernière instance de la chaîne "xrange" n'importe où dans le fichier).

Alors, pourquoi est-ce plus lent ?

Eh bien, d'une part, ils ont ajouté beaucoup de nouvelles fonctionnalités. D'autre part, ils ont apporté toutes sortes de changements un peu partout (surtout à l'intérieur de l'itération) qui ont des effets secondaires mineurs. Et il y a eu beaucoup de travail pour optimiser de manière spectaculaire divers cas importants, même si cela rend parfois légèrement pessimiste les cas moins importants. En ajoutant tout cela, je ne suis pas surpris que l'itération d'une range soit maintenant un peu plus lente. C'est l'un de ces cas moins importants que personne ne prendrait jamais assez de peine pour se concentrer dessus. Personne ne sera probablement jamais confronté à un cas d'utilisation réel où cette différence de performance est le point chaud de leur code.

157voto

gnibbler Points 103484

La plage de Python3 est le xrange de Python2. Pas besoin d'encapsuler un iter autour. Pour obtenir une liste réelle en Python3, vous devez utiliser list(range(...))

Si vous voulez quelque chose qui fonctionne avec Python2 et Python3, essayez ceci

try:
    xrange
except NameError:
    xrange = range

23voto

Blckknght Points 20780

Le type range de Python 3 fonctionne exactement comme xrange de Python 2. Je ne suis pas sûr de pourquoi vous constatez un ralentissement, puisque l'itérateur retourné par votre fonction xrange est exactement ce que vous obtiendriez si vous itériez directement sur range.

Je ne parviens pas à reproduire le ralentissement sur mon système. Voici comment j'ai testé :

Python 2, avec xrange:

Python 2.7.3 (default, Apr 10 2012, 23:24:47) [MSC v.1500 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import timeit
>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=100)
18.631936646865853

Python 3, avec range est un tout petit peu plus rapide :

Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import timeit
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)
17.31399508687869

J'ai récemment appris que le type range de Python 3 a d'autres fonctionnalités intéressantes, telles que le support pour le slicing : range(10,100,2)[5:25:5] est range(20, 60, 10) !

2voto

andrew pate Points 54

Une façon de corriger votre code python2 est :

import sys

si sys.version_info >= (3, 0):
    def xrange(*args, **kwargs):
        return iter(range(*args, **kwargs))

0voto

L'xrange de Python 2 est un générateur et implémente un itérateur tandis que range n'est qu'une fonction. En Python3 je ne sais pas pourquoi xrange a été abandonné.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X