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

273

Récemment, j'ai commencé à utiliser Python3 et son manque 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 donc? Je veux dire, pourquoi xrange a été supprimé? C'est un excellent outil pour apprendre. Pour les débutants, tout comme moi, comme nous l'étions tous à un moment donné. Pourquoi l'enlever? Quelqu'un peut-il m'indiquer le PEP approprié, je ne le trouve pas.

À votre santé.

catalesia
la source
231
rangeen Python 3.x est xrangede Python 2.x. Ce sont en fait les Python 2.x rangequi ont été supprimés.
Anorov
27
PS, vous ne devriez jamais le temps time. En plus d'être plus facile à utiliser et plus difficile de se tromper, et de répéter les tests pour vous, timeitprend soin de toutes sortes de choses dont vous ne vous souviendrez pas, ou même ne savez pas comment vous en occuper (comme désactiver le GC), et pouvez utiliser un horloge avec des milliers de fois une meilleure résolution.
abarnert
7
Aussi, pourquoi testez-vous le temps de filtrer le rangesur x%4 == 0? Pourquoi ne pas simplement tester list(xrange())vs list(range()), donc il y a aussi peu de travail étranger que possible? (Par exemple, comment savez-vous que 3.x ne fonctionne pas x%4plus lentement?) D'ailleurs, pourquoi construisez-vous un énorme list, ce qui implique beaucoup d'allocation de mémoire (qui, en plus d'être lent, est également incroyablement variable) ?
abarnert
5
Voir docs.python.org/3.0/whatsnew/3.0.html , section "Vues et itérateurs au lieu des listes": "range () se comporte désormais comme xrange (), sauf qu'il fonctionne avec des valeurs de taille arbitraire. n'existe plus." Ainsi, range retourne maintenant un itérateur. iter(range)est redondant.
ToolmakerSteve
9
Désolé, réalisé que citer le document de changement ne le rend pas aveuglément évident. Pour quiconque est confus et ne veut pas lire la longue réponse acceptée et tous ses commentaires: partout où vous utilisiez xrange en python 2, utilisez range en python 3. Il fait ce que xrange faisait, ce qui est retourner un itérateur. Si vous avez besoin des résultats dans une liste, faites-le list(range(..)). Cela équivaut à la plage de python 2. Ou pour le dire d'une autre manière: xrange a été renommé plage, car c'est la meilleure valeur par défaut; il n'était pas nécessaire d'avoir les deux, list(range)si vous avez vraiment besoin d'une liste. .
ToolmakerSteve

Réponses:

175

Certaines mesures de performances, en utilisant timeitau lieu d'essayer de le faire manuellement avec time.

Tout d'abord, Apple 2.7.2 64 bits:

In [37]: %timeit collections.deque((x for x in xrange(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.05 s per loop

Maintenant, python.org 3.3.0 64 bits:

In [83]: %timeit collections.deque((x for x in range(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.32 s per loop

In [84]: %timeit collections.deque((x for x in xrange(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.31 s per loop

In [85]: %timeit collections.deque((x for x in iter(range(10000000)) if x%4 == 0), maxlen=0) 
1 loops, best of 3: 1.33 s per loop

Apparemment, 3.x rangeest vraiment un peu plus lent que 2.x xrange. Et la xrangefonction de l'OP n'a rien à voir avec cela. (Pas étonnant, car un appel unique à l' __iter__emplacement n'est probablement pas visible parmi 10000000 appels à tout ce qui se passe dans la boucle, mais quelqu'un l'a évoqué comme une possibilité.)

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

Mais est-ce que c'est vraiment important? Vérifiez cela, avec 3.3.0 64 bits à nouveau:

In [86]: %timeit [x for x in range(10000000) if x%4 == 0]
1 loops, best of 3: 3.65 s per loop

Ainsi, la construction de listprend 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 rangesoit exactement de la même taille qu'un 2.x xrange- et, même s'il était 10 fois plus grand, la construction de la liste inutile est toujours environ 10000000x plus un problème que tout ce que l'itération de plage pourrait faire.

Et qu'en est-il d'une forboucle explicite au lieu de la boucle C à l'intérieur 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 loops, best of 3: 1.85 s per loop

Donc, presque autant de temps perdu dans la fordéclaration que dans le travail réel d'itération de la range.

Si vous craignez d'optimiser l'itération d'un objet de plage, vous cherchez probablement au mauvais endroit.


Pendant ce temps, vous continuez à demander pourquoi a xrangeété supprimé, peu importe le nombre de fois que 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é rangeet le 2.x rangeest ce qui a été supprimé.

Voici une preuve que l' rangeobjet 3.3 est un descendant direct de l' xrangeobjet 2.x (et non de la rangefonction 2.x ): la source de 3.3range et 2.7xrange . Vous pouvez même voir l' historique des modifications (lié, je crois, à la modification 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, pour commencer, ils ont ajouté beaucoup de nouvelles fonctionnalités. D'autre part, ils ont fait toutes sortes de changements partout (en particulier à l'intérieur de l'itération) qui ont des effets secondaires mineurs. Et il y avait eu beaucoup de travail pour optimiser considérablement divers cas importants, même si cela pessimise parfois légèrement les cas moins importants. Ajoutez tout cela, et je ne suis pas surpris que l'itération rangeaussi rapide que possible soit maintenant un peu plus lente. C'est l'un de ces cas moins importants sur lesquels personne ne se soucierait jamais suffisamment de se concentrer. Personne n'est susceptible d'avoir un cas d'utilisation réel où cette différence de performances est le point chaud dans leur code.

abarnert
la source
Mais c'est seulement 30% plus lent. Encore plus lent, mais une excellente réponse, quelque chose à penser. Cela ne répond pas à ma question: pourquoi a-t-on supprimé xrange ?? Pensez-y de cette façon - si vous aviez une application dépendante des performances basée sur le multitraitement sachant combien de file d'attente vous devez consommer à la fois, 30% feraient-ils une différence ou non? Vous voyez, vous dites que cela n'a pas d'importance, mais chaque fois que j'utilise la gamme, j'entends un énorme son de ventilateur pénible, ce qui signifie que le processeur est au pire, tandis que xrange ne le fait pas. Pensez-y;)
catalesia
9
@catalesia: Encore une fois, il n'a pas été supprimé, il a juste été renommé range. L' rangeobjet en 3.3 est un descendant direct de l' xrangeobjet en 2.7, pas de la rangefonction en 2.7. C'est comme demander alors que a itertools.imapété supprimé en faveur de map. Il n'y a pas de réponse, car rien de tel ne s'est produit.
abarnert
1
@catalesia: Les changements mineurs de performances ne sont probablement pas le résultat d'une décision de conception directe de ralentir les gammes, mais un effet secondaire de 4 ans de changements partout dans Python qui ont rendu beaucoup de choses plus rapides, certaines un peu plus lentes (et certaines choses plus rapide sur x86_64 mais plus lente sur x86, ou plus rapide dans certains cas d'utilisation mais plus lente dans d'autres, etc.). Personne n'était probablement préoccupé par une différence de 30% dans les deux cas dans le temps qu'il faut pour répéter un rangecertain temps sans rien faire d'autre.
abarnert
1
"Personne ne s'inquiétait probablement d'une différence de 30% dans les deux cas quant au temps qu'il faut pour parcourir une plage sans rien faire d'autre. " Exactement.
catalesia
18
@catalesia: Oui, exactement. Mais vous semblez penser que cela signifie le contraire de ce qu'il dit. Ce n'est pas un cas d'utilisation dont personne ne se souciera jamais, donc personne n'a remarqué qu'il était 30% plus lent. Et alors? Si vous pouvez trouver un programme réel qui s'exécute plus lentement dans Python 3.3 que dans 2.7 (ou 2.6) à cause de cela, les gens s'en soucieront. Si vous ne le pouvez pas, ils ne le feront pas, et vous ne devriez pas non plus.
abarnert
141

La gamme de python3 est le xrange de python2. Il n'est pas nécessaire d'envelopper un iter autour de lui. Pour obtenir une liste réelle en Python3, vous devez utiliserlist(range(...))

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

try:
    xrange
except NameError:
    xrange = range
John La Rooy
la source
1
Parfois, vous avez besoin de code qui fonctionne à la fois en Python 2 et 3. C'est une bonne solution.
Greg Glockner
3
Le problème est qu'avec cela, le code qui utilise les deux rangeet xrangese comportera différemment. Il ne suffit pas de le faire, il faudrait également s'assurer de ne jamais supposer que cela rangerenvoie une liste (comme ce serait le cas en python 2).
LangeHaare
Vous pouvez utiliser xrange de ce projet. Il existe un futurizeoutil pour convertir automatiquement votre code source: python-future.org/…
guettli
17

Le rangetype de Python 3 fonctionne exactement comme Python 2 xrange. Je ne sais pas pourquoi vous voyez un ralentissement, car l'itérateur renvoyé par votre xrangefonction est exactement ce que vous obtiendriez si vous répétiez rangedirectement.

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 rangeest 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 rangetype de Python 3 possède d'autres fonctionnalités intéressantes, telles que la prise en charge du découpage: range(10,100,2)[5:25:5]c'est range(20, 60, 10)!

Blckknght
la source
Peut-être que le ralentissement provient de la recherche de la nouvelle xrangefois tant de fois, ou est-ce fait qu'une seule fois?
askewchan
Un itérateur augmente-t-il réellement la vitesse de toute façon? Je pensais que ça sauvait juste de la mémoire.
askewchan
3
@catalesia Je pense que le point ici est qu'il xrangen'a pas été supprimé, juste renommé .
askewchan
1
@Blckknght: Cheers, mais ça craint quand même d'avoir une explication comme: "Set littéraux et compréhensions [19] [20] [done] {x} signifie set ([x]); {x, y} signifie set ([ x, y]). {F (x) pour x dans S si P (x)} signifie ensemble (F (x) pour x dans S si P (x)). NB. {range (x)} signifie ensemble ( [range (x)]), NOT set (range (x)). Il n'y a pas de littéral pour un ensemble vide; utilisez set () (ou {1} & {2} :-). Il n'y a pas de littéral frozenset; ils le sont aussi rarement nécessaire. "
catalesia
3
Pour rangemoi, la plus grosse victoire en 3.x est le temps constant __contains__. Les débutants écrivaient 300000 in xrange(1000000)et cela faisait itérer l'ensemble xrange(ou au moins les 30 premiers%), nous avons donc dû expliquer pourquoi c'était une mauvaise idée, même si cela avait l'air si pythonique. Maintenant, il est pythonique.
abarnert
1

Une façon de corriger votre code python2 est:

import sys

if sys.version_info >= (3, 0):
    def xrange(*args, **kwargs):
        return iter(range(*args, **kwargs))
Andrew Pate
la source
1
Le point est en python3 xrange n'est pas défini, donc le code hérité qui utilisait les coupures de xrange.
andrew pate
non, définissez simplement ce range = xrangequi est en commentaire par @John La Roy
mimi.vx
@ mimi.vx Pas sûr que range = xrange fonctionnerait en Python3 car xrange n'est pas défini. Mon commentaire se réfère au cas où vous avez un ancien code hérité qui contient des appels xrange ET que vous essayez de le faire fonctionner sous python3.
andrew pate
1
Ah, ma mauvaise .. xrange = range... j'ai changé de relevé
mimi.vx
La gamme EST un itérateur, et de toute façon, ce serait une idée terrible même si ce n'était pas le cas, car elle doit déballer toute la gamme en premier et perd les avantages d'utiliser un itérateur pour ce genre de chose. Donc, la réponse correcte n'est pas "range = xrange" its "xrange = range"
Shayne
0

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

Michel Fernandes
la source
Non, la portée n'est pas un interacteur. Vous ne pouvez pas faire next () avec cette structure. Pour plus d'informations, vous pouvez consulter ici treyhunner.com/2018/02/python-range-is-not-an-iterator
Michel Fernandes
Merci beaucoup pour la clarification. Mais je vais reformuler l'intention du commentaire original, et c'est que PY3 range()est l'équivalent de PY2 xrange(). Et donc dans PY3 xrange()est redondant.
Stephen Rauch
-2

comp: ~ $ python Python 2.7.6 (par défaut, 22 juin 2015, 17:58:13) [GCC 4.8.2] sur linux2

>>> import timeit
>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=100)

5.656799077987671

>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=100)

5.579368829727173

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)

21.54827117919922

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)

22.014557123184204

Avec timeit number = 1 param:

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=1)

0,2245171070098877

>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=1)

0,10750913619995117

comp: ~ $ python3 Python 3.4.3 (par défaut, 14 octobre 2015, 20:28:29) [GCC 4.8.4] sous linux

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)

9.113872020003328

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)

9.07014398300089

Avec timeit number = 1,2,3,4 param fonctionne rapidement et de manière linéaire:

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=1)

0,09329321900440846

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=2)

0,18501482300052885

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=3)

0,2703447980020428

>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=4)

0,36209142999723554

Il semble donc que si nous mesurons 1 cycle de boucle en cours comme timeit.timeit ("[x pour x dans la plage (1000000) si x% 4]", nombre = 1) (comme nous l'utilisons réellement dans le code réel) python3 fonctionne assez rapidement, mais dans les boucles répétées, python 2 xrange () gagne en vitesse contre range () de python 3.

dmitriy
la source
mais c'est par la langue elle-même ... rien à voir avec xrange / range.
mimi.vx