Sollten Sie immer xrange () gegenüber range () bevorzugen?

460

Warum oder warum nicht?

mbac32768
quelle
36
Kann jemand kurz den Unterschied zwischen den beiden für uns Nicht-Python-Typen beschreiben? Vielleicht macht so etwas wie "xrange () alles, was range () macht, unterstützt aber auch X, Y und Z"
Outlaw Programmer
87
range (n) erstellt eine Liste mit allen Ganzzahlen 0..n-1. Dies ist ein Problem, wenn Sie Range (1000000) ausführen, da Sie am Ende eine Liste mit> 4 MB erhalten. xrange behandelt dies, indem es ein Objekt zurückgibt, das vorgibt, eine Liste zu sein, aber nur die benötigte Nummer aus dem angeforderten Index berechnet und diese zurückgibt.
Brian
4
Siehe stackoverflow.com/questions/94935
James McMahon
4
Im Grunde genommen, während range(1000)ein ist list, xrange(1000)ist ein Objekt , das wie ein wirkt generator(obwohl es ist sicherlich nicht ein). Auch xrangeist schneller. Sie können import timeit from timeitund dann eine Methode erstellen , die nur eine for i in xrange: passandere hat range, dann tun timeit(method1)und timeit(method2)und, siehe da, xrange ist manchmal fast doppelt so schnell (wenn Sie keine Liste benötigen). (Für mich, für i in xrange(1000):passvs für i in range(1000):passdauerte 13.316725969314575vs 21.190124988555908Sekunden jeweils - das ist eine Menge.)
dylnmc
Ein weiterer Leistungstest ergibt xrange(100)20% schneller als range(100).
Evgeni Sergeev

Antworten:

443

Die Leistung xrange()ist normalerweise besser , insbesondere wenn Sie über einen großen Bereich iterieren . Es gibt jedoch noch einige Fälle, die Sie bevorzugen könnten range():

  • Tut in Python 3, range()was xrange()früher getan wurde und xrange()nicht existiert. Wenn Sie Code schreiben möchten, der sowohl auf Python 2 als auch auf Python 3 ausgeführt wird, können Sie diesen nicht verwenden xrange().

  • range()kann in einigen Fällen sogar schneller sein - z. wenn Sie dieselbe Sequenz mehrmals durchlaufen. xrange()muss das Integer-Objekt jedes Mal rekonstruieren, hat aber range()echte Integer-Objekte. (Es wird jedoch immer schlechter in Bezug auf das Gedächtnis abschneiden)

  • xrange()ist nicht in allen Fällen verwendbar, in denen eine echte Liste benötigt wird. Beispielsweise werden keine Slices oder Listenmethoden unterstützt.

[Bearbeiten] In einigen Beiträgen wird erwähnt, wie range()das 2to3-Tool aktualisiert wird. Hier ist die Ausgabe des Ausführens des Tools für einige Beispielverwendungen von range()undxrange()

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)

Wie Sie sehen können, bleibt der Bereich unverändert, wenn er in einer for-Schleife oder einem Verständnis verwendet wird oder wenn er bereits mit list () umschlossen ist.

Brian
quelle
5
Was meinst du mit "Reichweite wird zum Iterator"? Sollte dies nicht "Generator" sein?
Michael Mior
4
Nein. Der Generator bezieht sich auf einen bestimmten Iteratortyp, und der neue rangeist ohnehin kein Iterator.
user2357112 unterstützt Monica
Ihre zweite Kugel macht eigentlich keinen Sinn. Sie sagen, dass Sie ein Objekt nicht mehrmals verwenden können. sicher kannst du! versuche xr = xrange(1,11)dann in der nächsten Zeile for i in xr: print " ".join(format(i*j,"3d") for j in xr)und voila! Sie haben Ihre Stundenpläne bis zu zehn. Es funktioniert genauso wie r = range(1,11)und for i in r: print " ".join(format(i*j,"3d") for j in r)... alles ist ein Objekt in Python2. Ich denke, was Sie damit sagen wollten, ist, dass Sie indexbasiertes Verständnis (wenn das Sinn macht) besser machen können rangeals mit xrange. Reichweite ist sehr selten handlich, denke ich
dylnmc
(Nicht genug Platz) Obwohl ich tun denken , dass rangekann sehr nützlich sein , wenn Sie eine verwenden mögen listin einer Schleife und dann bestimmte Indizes auf diese Liste auf der Grundlage bestimmter Bedingungen oder Anfügen Dinge zu ändern, dann rangeist besser auf jeden Fall. Es xrangeist jedoch einfach schneller und benötigt weniger Speicher, sodass es für die meisten Schleifenanwendungen am besten zu sein scheint. Es gibt Fälle - zurückgehend auf die Frage des Fragestellers - selten, aber vorhanden, wo rangees besser wäre. Vielleicht nicht so selten wie ich denke, aber ich benutze xrange95% der Zeit.
dylnmc
129

Nein, beide haben ihre Verwendung:

Verwenden Sie diese xrange()Option beim Iterieren, da dadurch Speicherplatz gespart wird. Sagen:

for x in xrange(1, one_zillion):

eher, als:

for x in range(1, one_zillion):

Verwenden range()Sie andererseits, wenn Sie tatsächlich eine Liste von Zahlen möchten.

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

Sie sollten bevorzugen range()über xrange()nur , wenn Sie eine aktuelle Liste benötigen. Zum Beispiel, wenn Sie die von zurückgegebene Liste ändern möchten range()oder wenn Sie sie in Scheiben schneiden möchten. Für die Iteration oder auch nur für die normale Indizierung xrange()funktioniert es gut (und normalerweise viel effizienter). Es gibt einen Punkt, an dem range()es etwas schneller ist als xrange()bei sehr kleinen Listen, aber abhängig von Ihrer Hardware und verschiedenen anderen Details kann die Gewinnschwelle bei Länge 1 oder 2 liegen. kein Grund zur Sorge. Lieber xrange().

Thomas Wouters
quelle
30

Ein weiterer Unterschied besteht darin, dass xrange () keine Zahlen unterstützen kann, die größer als C ints sind. Wenn Sie also einen Bereich mit der in Python integrierten Unterstützung für große Zahlen haben möchten, müssen Sie range () verwenden.

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 hat dieses Problem nicht:

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

xrange()ist effizienter, da anstelle einer Liste von Objekten jeweils nur ein Objekt generiert wird. Anstelle von 100 Ganzzahlen und dem gesamten Overhead sowie der Liste, in die sie eingefügt werden sollen, haben Sie jeweils nur eine Ganzzahl. Schnellere Generierung, bessere Speichernutzung, effizienterer Code.

Wenn ich nicht speziell eine Liste für etwas brauche, bevorzuge ich immer xrange()

Dan Udey
quelle
8

range () gibt eine Liste zurück, xrange () gibt ein xrange-Objekt zurück.

xrange () ist etwas schneller und speichereffizienter. Aber der Gewinn ist nicht sehr groß.

Der zusätzliche Speicher, der von einer Liste verwendet wird, wird natürlich nicht nur verschwendet, Listen haben mehr Funktionen (Slice, Repeat, Insert, ...). Genaue Unterschiede finden Sie in der Dokumentation . Es gibt keine Bonehard-Regel, verwenden Sie, was benötigt wird.

Python 3.0 befindet sich noch in der Entwicklung, aber IIRC range () wird xrange () von 2.X sehr ähnlich sein und list (range ()) kann zum Generieren von Listen verwendet werden.

jschultz
quelle
5

Ich möchte nur sagen, dass es WIRKLICH nicht so schwierig ist, ein xrange-Objekt mit Slice- und Indizierungsfunktionalität zu erhalten. Ich habe einen Code geschrieben, der ziemlich gut funktioniert und genauso schnell ist wie xrange, wenn es darauf ankommt (Iterationen).

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)

Ehrlich gesagt denke ich, dass das ganze Problem irgendwie albern ist und xrange sollte das alles sowieso tun ...

Garrett Berg
quelle
Ja stimmte zu; Überprüfen Sie anhand einer völlig anderen Technologie die Arbeit an lodash, um es faul zu machen: github.com/lodash/lodash/issues/274 . Das Schneiden usw. sollte immer noch so faul wie möglich sein und wo nicht, erst dann wiederholen.
Rob Grant
4

Ein gutes Beispiel aus dem Buch: Practical Python von Magnus Lie Hetland

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

Ich würde im vorherigen Beispiel nicht empfehlen, range anstelle von xrange zu verwenden. Obwohl nur die ersten fünf Zahlen benötigt werden, berechnet range alle Zahlen, und das kann viel Zeit in Anspruch nehmen. Mit xrange ist dies kein Problem, da nur die benötigten Zahlen berechnet werden.

Ja, ich habe die Antwort von @ Brian gelesen: In Python 3 ist range () sowieso ein Generator und xrange () existiert nicht.

Grijesh Chauhan
quelle
3

Gehen Sie aus folgenden Gründen mit Reichweite:

1) xrange wird in neueren Python-Versionen verschwinden. Dies gibt Ihnen eine einfache Zukunftskompatibilität.

2) Der Bereich übernimmt die mit xrange verbundenen Wirkungsgrade.

torial
quelle
13
Tu das nicht. xrange () wird verschwinden, aber auch viele andere Dinge. Das Tool, mit dem Sie Ihren Python 2.x-Code in Python 3.x-Code übersetzen, übersetzt xrange () automatisch in range (), range () wird jedoch in die weniger effiziente Liste (range ()) übersetzt.
Thomas Wouters
10
Thomas: Es ist eigentlich ein bisschen schlauer als das. Es übersetzt range () in Situationen, in denen es eindeutig keine echte Liste benötigt (z. B. in einer for-Schleife oder einem Verständnis), nur in range (). Nur Fälle, in denen es einer Variablen zugewiesen oder direkt verwendet wird, müssen mit list ()
Brian
2

Okay, jeder hier hat eine andere Meinung zu den Kompromissen und Vorteilen von xrange gegenüber range. Sie sind größtenteils korrekt, xrange ist ein Iterator, und der Bereich wird konkretisiert und erstellt eine tatsächliche Liste. In den meisten Fällen werden Sie keinen Unterschied zwischen den beiden bemerken. (Sie können eine Karte mit Reichweite verwenden, jedoch nicht mit xrange, sie verbraucht jedoch mehr Speicher.)

Was ich denke, dass Sie Rallye hören möchten, ist, dass die bevorzugte Wahl xrange ist. Da range in Python 3 ein Iterator ist, konvertiert das Codekonvertierungstool 2to3 alle Verwendungen von xrange korrekt in range und gibt einen Fehler oder eine Warnung für die Verwendung von range aus. Wenn Sie sicher sein möchten, dass Sie Ihren Code in Zukunft problemlos konvertieren können, verwenden Sie nur xrange und list (xrange), wenn Sie sicher sind, dass Sie eine Liste möchten. Das habe ich beim CPython-Sprint auf der PyCon in diesem Jahr (2008) in Chicago gelernt.

Douglas Mayle
quelle
8
Das ist nicht wahr. Code wie "für x in Bereich (20)" wird als Bereich belassen, und Code wie "x = Bereich (20)" wird in "x = Liste (Bereich (20))" konvertiert - keine Fehler. Wenn Sie Code schreiben möchten, der sowohl unter 2.6 als auch unter 3.0 ausgeführt wird, ist range () Ihre einzige Option, ohne Kompatibilitätsfunktionen hinzuzufügen.
Brian
2
  • range(): range(1, 10)Gibt eine Liste mit 1 bis 10 Zahlen zurück und speichert die gesamte Liste.
  • xrange(): Like range(), aber anstatt eine Liste zurückzugeben, wird bei Bedarf ein Objekt zurückgegeben, das die Zahlen im Bereich generiert. Für Schleifen ist dies etwas schneller als range()und speichereffizienter. xrange()Objekt wie ein Iterator und generiert die Zahlen bei Bedarf (Lazy Evaluation).
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()Funktioniert genauso wie xrange()in Python 3 und es gibt keinen Begriff xrange()in Python 3. range()In einigen Szenarien kann dies sogar schneller sein, wenn Sie dieselbe Sequenz mehrmals durchlaufen. xrange()muss das Integer-Objekt jedes Mal rekonstruieren, hat aber range()echte Integer-Objekte.

Tushar.PUCSD
quelle
2

Es xrangeist zwar schneller als rangein den meisten Fällen, aber der Leistungsunterschied ist ziemlich gering. Das kleine Programm unten vergleicht das Iterieren über a rangeund an 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)

Die folgenden Ergebnisse zeigen, dass dies xrangezwar schneller ist, aber nicht ausreicht, um darüber zu schwitzen.

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

Verwenden xrangeSie es also auf jeden Fall , aber machen Sie sich keine Sorgen, es sei denn, Sie verwenden eine eingeschränkte Hardware.

Geschwindigkeitsflugzeug
quelle
Ihr list_lennicht benutzt wird und so dass Sie nur den Code für die Listen der Länge ausgeführt 100.
Mark
Ich würde empfehlen, die Listenlänge tatsächlich zu ändern:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Mark
Wow, das wird eine gute Woche, danke, behoben. Trotzdem kein großer Unterschied.
Speedplane