Warum gibt es in Python3 keine xrange-Funktion?

273

Vor kurzem habe ich angefangen, Python3 zu verwenden, und es fehlt xrange weh.

Einfaches Beispiel:

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()

Die Ergebnisse sind jeweils:

1) 1,53888392448 2) 3,215819835662842

Warum ist das so? Ich meine, warum wurde xrange entfernt? Es ist so ein tolles Werkzeug zum Lernen. Für die Anfänger, genau wie ich, als wären wir alle irgendwann. Warum es entfernen? Kann mich jemand auf den richtigen PEP hinweisen, ich kann ihn nicht finden.

Prost.

Katalesie
quelle
231
rangein Python 3.x ist xrangevon Python 2.x. Es war tatsächlich Python 2.x, rangedas entfernt wurde.
Anorov
27
PS, du solltest nie Zeit mit time. Abgesehen davon, dass es einfacher zu bedienen und schwerer zu verwechseln ist und Tests für Sie wiederholt, timeitkümmert es sich um alle möglichen Dinge, an die Sie sich nicht erinnern oder die Sie nicht einmal wissen (wie das Deaktivieren des GC), und verwendet möglicherweise a Uhr mit tausendfach besserer Auflösung.
abarnert
7
Auch, warum testen Sie die Zeit zu filtern , die rangeauf x%4 == 0? Warum nicht einfach testen list(xrange())vs. list(range()), damit so wenig Fremdarbeit wie möglich geleistet wird? (Woher wissen Sie zum Beispiel, dass 3.x nicht x%4langsamer arbeitet?) Warum bauen Sie eine riesige Datei auf list, die eine Menge Speicherzuweisung erfordert (die nicht nur langsam ist, sondern auch unglaublich variabel) ? ?
abarnert
5
Siehe docs.python.org/3.0/whatsnew/3.0.html , Abschnitt "Ansichten und Iteratoren anstelle von Listen": "range () verhält sich jetzt wie xrange (), außer dass es mit Werten beliebiger Größe funktioniert. Letzteres existiert nicht mehr." Range gibt jetzt einen Iterator zurück. iter(range)ist redundant.
ToolmakerSteve
9
Entschuldigung, das Zitieren des Änderungsdokuments macht es nicht blind offensichtlich. Für alle anderen, die verwirrt sind und die lange akzeptierte Antwort und alle ihre Kommentare nicht lesen möchten: Wo immer Sie xrange in Python 2 verwendet haben, verwenden Sie range in Python 3. Es macht das, was xrange früher getan hat, nämlich einen Iterator zurückgeben. Wenn Sie die Ergebnisse in einer Liste benötigen, tun Sie dies list(range(..)). Dies entspricht der Reichweite von Python 2. Oder anderslist(range) ausgedrückt: xrange wurde in range umbenannt, da dies die bessere Standardeinstellung ist. Es war nicht notwendig, beides zu haben, wenn Sie wirklich eine Liste brauchen. .
ToolmakerSteve

Antworten:

175

Einige Leistungsmessungen verwenden, timeitanstatt zu versuchen, dies manuell zu tun time.

Zunächst Apple 2.7.2 64-Bit:

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

Jetzt 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 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

Offenbar 3.x rangeist wirklich ein bisschen langsamer als 2.x xrange. Und die xrangeFunktion des OP hat nichts damit zu tun. (Kein Wunder, da ein einmaliger Anruf an den __iter__Slot unter 10000000 Anrufen für alles, was in der Schleife passiert, wahrscheinlich nicht sichtbar ist, aber jemand hat ihn als Möglichkeit angesprochen.)

Aber es ist nur 30% langsamer. Wie wurde das OP 2x so langsam? Wenn ich die gleichen Tests mit 32-Bit-Python wiederhole, erhalte ich 1,58 gegenüber 3,12. Ich vermute also, dass dies ein weiterer Fall ist, in dem 3.x für 64-Bit-Leistung in einer Weise optimiert wurde, die 32-Bit-Leistung beeinträchtigt.

Aber ist das wirklich wichtig? Überprüfen Sie dies mit 3.3.0 64-Bit erneut:

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

Das Erstellen von listdauert also mehr als doppelt so lange wie die gesamte Iteration.

Und was "verbraucht viel mehr Ressourcen als Python 2.6+" betrifft, so sieht es aus meinen Tests so aus, als ob ein 3.x rangegenau die gleiche Größe wie ein 2.x hat xrange- und selbst wenn es 10x so groß wäre, die unnötige Liste erstellt ist immer noch ungefähr 10000000x mehr ein Problem als alles, was die Bereichsiteration möglicherweise tun könnte.

Und was ist mit einer expliziten forSchleife anstelle der C-Schleife im Inneren 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

Es wurde also fast so viel Zeit in der forAussage verschwendet wie in der eigentlichen Arbeit der Iteration der range.

Wenn Sie sich Gedanken über die Optimierung der Iteration eines Bereichsobjekts machen, suchen Sie wahrscheinlich an der falschen Stelle.


In der Zwischenzeit fragen Sie immer wieder, warum xrangeentfernt wurde, egal wie oft Ihnen die Leute dasselbe sagen, aber ich werde es noch einmal wiederholen: Es wurde nicht entfernt: Es wurde umbenannt in rangeund das 2.x rangewurde entfernt.

Hier ist ein Beweis dafür, dass das 3.3- rangeObjekt ein direkter Nachkomme des 2.x- xrangeObjekts (und nicht der 2.x- rangeFunktion) ist: die Quelle für 3.3range und 2.7xrange . Sie können sogar den Änderungsverlauf sehen (der meiner Meinung nach mit der Änderung verknüpft ist, die die letzte Instanz der Zeichenfolge "xrange" an einer beliebigen Stelle in der Datei ersetzt hat).

Warum ist es langsamer?

Zum einen haben sie viele neue Funktionen hinzugefügt. Zum anderen haben sie überall alle Arten von Änderungen vorgenommen (insbesondere innerhalb der Iteration), die geringfügige Nebenwirkungen haben. Und es gab viel Arbeit, um verschiedene wichtige Fälle dramatisch zu optimieren, auch wenn es manchmal weniger wichtige Fälle leicht pessimiert. Addiere das alles und ich bin nicht überrascht, dass das Iterieren eines rangeso schnellen wie möglich jetzt etwas langsamer ist. Es ist einer dieser weniger wichtigen Fälle, auf die sich niemand jemals genug konzentrieren würde. Es ist wahrscheinlich, dass niemand jemals einen realen Anwendungsfall hat, bei dem dieser Leistungsunterschied der Hotspot in seinem Code ist.

abarnert
quelle
Aber es ist nur 30% langsamer. Immer noch langsamer, aber ein großartiger Reaktionspartner, über den man nachdenken sollte. Es beantwortet jedoch nicht meine Frage: Warum wurde xrange entfernt? Stellen Sie sich das so vor: Wenn Sie eine leistungsabhängige App auf der Basis von Multiprocessing hätten und wüssten, wie viel Warteschlange Sie pro Zeit verbrauchen müssen, würden 30% einen Unterschied machen oder nicht? Du siehst, du sagst, es spielt keine Rolle, aber jedes Mal, wenn ich Range benutze, höre ich diesen riesigen, quälenden Fan-Sound, was bedeutet, dass die CPU eingeschaltet ist, während xrange es nicht tut. Denken Sie darüber nach;)
catalesia
9
@catalesia: Noch einmal, es wurde nicht entfernt, es wurde nur umbenannt range. Das rangeObjekt in 3.3 ist ein direkter Nachkomme des xrangeObjekts in 2.7, nicht der rangeFunktion in 2.7. Es ist wie zu fragen, während itertools.imapzugunsten von entfernt wurde map. Es gibt keine Antwort, weil so etwas nicht passiert ist.
abarnert
1
@catalesia: Die geringfügigen Leistungsänderungen sind vermutlich nicht das Ergebnis einer direkten Entwurfsentscheidung, Bereiche langsamer zu machen, sondern ein Nebeneffekt von 4 Jahren Änderungen in ganz Python, die viele Dinge schneller, einige Dinge etwas langsamer (und einige Dinge) gemacht haben schneller auf x86_64, aber langsamer auf x86 oder schneller in einigen Anwendungsfällen, aber langsamer in anderen usw.). Niemand war wahrscheinlich besorgt über einen Unterschied von 30%, wie lange es dauert, eine rangeWeile zu iterieren und nichts anderes zu tun.
Abarnert
1
"Niemand war wahrscheinlich besorgt über einen Unterschied von 30%, wie lange es dauert, einen Bereich zu iterieren , ohne etwas anderes zu tun. " Genau.
Catalesia
18
@catalesia: Ja genau. Aber Sie scheinen zu glauben, dass dies das Gegenteil von dem bedeutet, was es sagt. Es ist kein Anwendungsfall, den irgendjemand jemals interessieren wird, daher hat niemand bemerkt, dass es 30% langsamer wurde. Na und? Wenn Sie aus diesem Grund ein reales Programm finden, das in Python 3.3 langsamer ausgeführt wird als in 2.7 (oder 2.6), werden sich die Leute darum kümmern. Wenn Sie nicht können, werden sie nicht und Sie sollten auch nicht.
abarnert
141

Python3 Palette ist xrange des Python2. Es ist nicht erforderlich, einen Iter darum zu wickeln. Um eine aktuelle Liste in Python3 zu erhalten, müssen Sie verwendenlist(range(...))

Wenn Sie etwas möchten, das mit Python2 und Python3 funktioniert, versuchen Sie dies

try:
    xrange
except NameError:
    xrange = range
John La Rooy
quelle
1
Manchmal benötigen Sie Code, der sowohl in Python 2 als auch in Python 3 funktioniert. Dies ist eine gute Lösung.
Greg Glockner
3
Das Problem ist, dass Code, der beide verwendet rangeund xrangesich unterschiedlich verhält. Es reicht nicht aus, dies zu tun, man müsste auch sicherstellen, dass man niemals davon ausgeht, dass rangeeine Liste zurückgegeben wird (wie es in Python 2 der Fall wäre).
LangeHaare
Sie können xrange aus diesem Projekt verwenden. Es gibt ein futurizeTool, mit dem Sie Ihren
guettli
17

Der rangeTyp von Python 3 funktioniert genauso wie der von Python 2 xrange. Ich bin mir nicht sicher, warum Sie eine Verlangsamung sehen, da der von Ihrer xrangeFunktion zurückgegebene Iterator genau das ist, was Sie erhalten würden, wenn Sie rangedirekt iterieren würden .

Ich kann die Verlangsamung auf meinem System nicht reproduzieren. So habe ich getestet:

Python 2, mit 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 mit rangeist ein kleines bisschen schneller:

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

Ich habe kürzlich erfahren, dass der rangeTyp von Python 3 einige andere nette Funktionen hat, wie zum Beispiel die Unterstützung für das Schneiden: range(10,100,2)[5:25:5]is range(20, 60, 10)!

Blckknght
quelle
Vielleicht kommt die Verlangsamung xrangeso oft von der Suche nach dem Neuen , oder wird das nur einmal gemacht?
Askewchan
Erhöht ein Iterator tatsächlich die Geschwindigkeit? Ich dachte, es spart nur Speicher.
Askewchan
3
@catalesia Ich denke , der Punkt ist , dass xrangewurde nicht entfernt, nur umbenannt .
Askewchan
1
@Blckknght: Prost, aber es ist immer noch schade, eine Erklärung zu haben wie: "Setze Literale und Verständnis [19] [20] [erledigt] {x} bedeutet set ([x]); {x, y} bedeutet set ([ x, y]). {F (x) für x in S, wenn P (x)} bedeutet Menge (F (x) für x in S, wenn P (x)). NB. {Bereich (x)} bedeutet Menge ( [range (x)]), NOT set (range (x)). Es gibt kein Literal für eine leere Menge; benutze set () (oder {1} & {2} :-). Es gibt kein frozenset-Literal; sie sind es auch selten benötigt. "
catalesia
3
Der größte Gewinn in 3.x rangeist für mich die konstante Zeit __contains__. Neulinge schrieben früher 300000 in xrange(1000000)und das führte dazu, dass das Ganze xrange(oder zumindest die ersten 30%) wiederholt wurde. Deshalb mussten wir erklären, warum das eine schlechte Idee war, obwohl es so pythonisch aussieht. Nun, es ist pythonic.
Abarnert
1

Eine Möglichkeit, Ihren Python2-Code zu reparieren, ist:

import sys

if sys.version_info >= (3, 0):
    def xrange(*args, **kwargs):
        return iter(range(*args, **kwargs))
Andrew Pate
quelle
1
Der Punkt ist in Python3. Xrange ist nicht definiert, daher wird Legacy-Code, der xrange verwendet, unterbrochen.
Andrew Pate
nein, definieren Sie einfach range = xrangewie im Kommentar von @John La Roy
mimi.vx
@ mimi.vx Nicht sicher, ob range = xrange in Python3 funktioniert, da xrange nicht definiert ist. Mein Kommentar bezieht sich auf den Fall, dass Sie alten Legacy-Code haben, der xrange-Aufrufe enthält, UND dass Sie versuchen, ihn unter python3 zum Laufen zu bringen.
Andrew Pate
1
Ah, mein schlechtes ... xrange = range... ich habe die Aussagen gewechselt
mimi.vx
range IST ein Iiterator, und dies wäre ohnehin eine schreckliche Idee, selbst wenn dies nicht der Fall wäre, da zuerst der gesamte Bereich entpackt werden muss und die Vorteile der Verwendung eines Iterators für diese Art von Dingen verloren gehen. Die richtige Antwort lautet also nicht "range = xrange", sondern "xrange = range"
Shayne
0

xrange aus Python 2 ist ein Generator und implementiert einen Iterator, während range nur eine Funktion ist. In Python3 weiß ich nicht, warum der Bereich entfernt wurde.

Michel Fernandes
quelle
Nein, Range ist kein Interator. Mit dieser Struktur können Sie next () nicht ausführen. Weitere Informationen finden Sie hier treyhunner.com/2018/02/python-range-is-not-an-iterator
Michel Fernandes
Vielen Dank für die Klarstellung. Aber ich werde die Absicht des ursprünglichen Kommentars wiederholen, und das heißt, dass PY3 range()das Äquivalent von PY2 ist xrange(). Und somit ist in PY3 xrange()überflüssig.
Stephen Rauch
-2

comp: ~ $ python Python 2.7.6 (Standard, 22. Juni 2015, 17:58:13) [GCC 4.8.2] unter 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

Mit 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 (Standard, 14. Oktober 2015, 20:28:29) [GCC 4.8.4] unter 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

Mit timeit number = 1,2,3,4 arbeitet param schnell und linear:

>>> 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

Wenn wir also einen laufenden Schleifenzyklus wie timeit.timeit messen ("[x für x im Bereich (1000000) wenn x% 4]", Zahl = 1) (wie wir es tatsächlich in echtem Code verwenden), funktioniert python3 schnell genug. aber in wiederholten Schleifen gewinnt Python 2 xrange () in der Geschwindigkeit gegen range () von Python 3.

dmitriy
quelle
aber das ist von der Sprache selbst ... nichts mit xrange / range zu tun.
mimi.vx