Preferisci sempre xrange () over range ()?


460

Perché o perché no?


36
Qualcuno può descrivere brevemente la differenza tra i 2 per noi non-pitone? Forse qualcosa come "xrange () fa tutto quanto () fa, ma supporta anche X, Y e Z"
Outlaw Programmer,

87
range (n) crea un elenco contenente tutti gli interi 0..n-1. Questo è un problema se si esegue l'intervallo (1000000), perché si finirà con un elenco> 4 Mb. xrange si occupa di questo restituendo un oggetto che finge di essere un elenco, ma risolve semplicemente il numero necessario dall'indice richiesto e lo restituisce.
Brian,


4
Fondamentalmente, mentre range(1000)è a list, xrange(1000)è un oggetto che si comporta come un generator(anche se certamente non lo è ). Inoltre, xrangeè più veloce. Puoi import timeit from timeite quindi creare un metodo che ha appena for i in xrange: passe un altro per range, quindi fai timeit(method1)e timeit(method2)e, ecco ed ecco, xrange è quasi due volte più veloce a volte (cioè quando non hai bisogno di un elenco). (Per me, per i in xrange(1000):passvs per ha i in range(1000):passpreso rispettivamente 13.316725969314575vs 21.190124988555908secondi - è molto.)
dylnmc

Un altro test delle prestazionixrange(100)il 20% più veloce di range(100).
Evgeni Sergeev,

Risposte:


443

Per le prestazioni, specialmente quando stai iterando su una vasta gamma, di xrange()solito è meglio. Tuttavia, ci sono ancora alcuni casi per cui potresti preferire range():

  • In Python 3, range()fa quello che era xrange()solito fare e xrange()non esiste. Se si desidera scrivere codice che verrà eseguito su Python 2 e Python 3, non è possibile utilizzarlo xrange().

  • range()può effettivamente essere più veloce in alcuni casi, ad es. se scorre ripetutamente sulla stessa sequenza. xrange()deve ricostruire l'oggetto intero ogni volta, ma range()avrà oggetti interi reali. (Tuttavia, funzionerà sempre peggio in termini di memoria)

  • xrange()non è utilizzabile in tutti i casi in cui è necessario un elenco reale. Ad esempio, non supporta le sezioni o alcun metodo di elenco.

[Modifica] Ci sono un paio di post che menzionano come range()verrà aggiornato dallo strumento 2to3. Per la cronaca, ecco l'output dell'esecuzione dello strumento su alcuni esempi di utilizzo di range()exrange()

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)

Come puoi vedere, quando usato in un ciclo for o comprensione, o dove già avvolto con list (), l'intervallo rimane invariato.


5
Cosa intendi con "range diventerà un iteratore"? Questo non dovrebbe essere "generatore"?
Michael Mior,

4
No. Il generatore si riferisce a un tipo specifico di iteratore e il nuovo rangenon è comunque un iteratore.
user2357112 supporta Monica

Il tuo secondo proiettile non ha davvero senso. Stai dicendo che non puoi usare un oggetto più volte; certo che puoi! prova xr = xrange(1,11)quindi sulla riga successiva for i in xr: print " ".join(format(i*j,"3d") for j in xr)e voilà! Hai i tuoi orari fino a dieci. Funziona esattamente come r = range(1,11)e for i in r: print " ".join(format(i*j,"3d") for j in r)... tutto è un oggetto in Python2. Penso che ciò che intendevi dire è che puoi fare una comprensione basata sull'indice (se questo ha senso) meglio rangerispetto a xrange. Range è utile molto di rado, penso
dylnmc

(Non c'era abbastanza spazio) Anche se, io faccio penso che rangepuò essere utile se si desidera utilizzare un listin un ciclo e quindi modificare alcuni indici in base a determinate condizioni o cose aggiungere a quella lista, allora rangeè sicuramente meglio. Tuttavia, xrangeè semplicemente più veloce e utilizza meno memoria, quindi per la maggior parte delle applicazioni in loop, sembra essere il migliore. Ci sono casi - tornando alla domanda dell'interrogante - raramente ma esistenti, dove rangesarebbe meglio. Forse non di rado come sto pensando, ma certamente uso il xrange95% delle volte.
dylnmc,

129

No, entrambi hanno i loro usi:

Utilizzare xrange()durante l'iterazione, poiché consente di risparmiare memoria. Dire:

for x in xrange(1, one_zillion):

piuttosto che:

for x in range(1, one_zillion):

D'altra parte, utilizzare range()se si desidera effettivamente un elenco di numeri.

multiples_of_seven = range(7,100,7)
print "Multiples of seven < 100: ", multiples_of_seven

42

Si dovrebbe favorire range()sopra xrange()solo quando è necessario un elenco vero e proprio. Ad esempio, quando si desidera modificare l'elenco restituito da range()o quando si desidera suddividerlo. Per l'iterazione o anche solo l'indicizzazione normale, xrange()funzionerà bene (e di solito molto più efficientemente). C'è un punto in cui range()è un po 'più veloce rispetto xrange()a elenchi molto piccoli, ma a seconda dell'hardware e di vari altri dettagli, il break-even può essere un risultato di lunghezza 1 o 2; non qualcosa di cui preoccuparsi. Prefer xrange().


30

Un'altra differenza è che xrange () non può supportare numeri più grandi di C ints, quindi se vuoi avere un intervallo usando il supporto per numeri grandi incorporato in python, devi usare 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 non presenta questo problema:

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)

13

xrange()è più efficiente perché invece di generare un elenco di oggetti, genera solo un oggetto alla volta. Invece di 100 numeri interi, tutti i loro costi generali e l'elenco in cui inserirli, hai solo un numero intero alla volta. Generazione più veloce, migliore utilizzo della memoria, codice più efficiente.

A meno che non abbia specificamente bisogno di un elenco per qualcosa, preferisco sempre xrange()


8

range () restituisce un elenco, xrange () restituisce un oggetto xrange.

xrange () è un po 'più veloce e un po' più efficiente in termini di memoria. Ma il guadagno non è molto grande.

La memoria aggiuntiva utilizzata da un elenco ovviamente non è solo sprecata, gli elenchi hanno più funzionalità (suddivisione, ripetizione, inserimento, ...). Differenze esatte possono essere trovate nella documentazione . Non esiste una regola ossea, usa ciò che è necessario.

Python 3.0 è ancora in fase di sviluppo, ma la gamma IIRC () sarà molto simile a xrange () di 2.X e list (range ()) può essere usato per generare liste.


5

Vorrei solo dire che DAVVERO non è così difficile ottenere un oggetto xrange con funzionalità slice e indicizzazione. Ho scritto del codice che funziona abbastanza bene ed è veloce quanto xrange per quando conta (iterazioni).

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)

Onestamente, penso che l'intero problema sia un po 'sciocco e xrange dovrebbe fare tutto questo comunque ...


Sì d'accordo; da una tecnologia totalmente diversa, controlla il lavoro su lodash per renderlo pigro: github.com/lodash/lodash/issues/274 . Affettare ecc dovrebbe essere il più pigro possibile e dove no, solo allora reificare.
Rob Grant,

4

Un buon esempio nel libro: Practical Python di Magnus Lie Hetland

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

Non consiglierei di usare range invece di xrange nell'esempio precedente — sebbene siano necessari solo i primi cinque numeri, range calcola tutti i numeri e ciò potrebbe richiedere molto tempo. Con xrange, questo non è un problema perché calcola solo quei numeri necessari.

Sì, ho letto la risposta di Brian: in python 3, range () è comunque un generatore e xrange () non esiste.


3

Vai con gamma per questi motivi:

1) xrange andrà via nelle versioni più recenti di Python. Questo ti offre una facile compatibilità futura.

2) la gamma assumerà l'efficienza associata a xrange.


13
Non farlo xrange () andrà via, ma anche molte altre cose. Lo strumento che utilizzerai per tradurre il tuo codice Python 2.x in codice Python 3.x tradurrà automaticamente xrange () in range (), ma range () verrà tradotto nella lista meno efficiente (range ()).
Thomas Wouters,

10
Thomas: In realtà è un po 'più intelligente di così. Tradurrà range () in situazioni in cui chiaramente non ha bisogno di un vero elenco (ad esempio in un ciclo for, o comprensione) in solo range (). Solo i casi in cui viene assegnato a una variabile o utilizzato direttamente devono essere racchiusi in list ()
Brian

2

Va bene, tutti qui hanno un'opinione diversa riguardo ai compromessi e ai vantaggi di xrange rispetto alla gamma. Sono per lo più corretti, xrange è un iteratore e la gamma si estende e crea un elenco reale. Nella maggior parte dei casi, non noterai davvero una differenza tra i due. (Puoi usare la mappa con range ma non con xrange, ma usa più memoria.)

Quello che penso che tu voglia sentire, è che la scelta preferita è xrange. Poiché l'intervallo in Python 3 è un iteratore, lo strumento di conversione del codice 2to3 convertirà correttamente tutti gli usi di xrange in intervallo e genererà un errore o un avviso per gli usi dell'intervallo. Se vuoi essere sicuro di convertire facilmente il tuo codice in futuro, utilizzerai solo xrange e list (xrange) quando sei sicuro di volere un elenco. L'ho imparato durante lo sprint CPython al PyCon quest'anno (2008) a Chicago.


8
Non è vero. Il codice come "for x in range (20)" verrà lasciato come range e il codice come "x = range (20)" verrà convertito in "x = list (range (20))" - nessun errore. Inoltre, se si desidera scrivere codice che verrà eseguito sia in 2.6 che in 3.0, range () è l'unica opzione senza aggiungere funzioni di compatibilità.
Brian,

2
  • range(): range(1, 10)restituisce un elenco da 1 a 10 numeri e conserva l'intero elenco in memoria.
  • xrange(): Like range(), ma invece di restituire un elenco, restituisce un oggetto che genera i numeri nell'intervallo su richiesta. Per il looping, questo è leggermente più veloce range()e più efficiente della memoria. xrange()oggetto come un iteratore e genera i numeri su richiesta (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()fa la stessa cosa xrange()usata in Python 3 e non xrange()esiste un termine in Python 3. range()può effettivamente essere più veloce in alcuni scenari se si scorre ripetutamente la stessa sequenza. xrange()deve ricostruire l'oggetto intero ogni volta, ma range()avrà oggetti interi reali.


2

Mentre xrangeè più veloce che rangenella maggior parte dei casi, la differenza nelle prestazioni è piuttosto minima. Il piccolo programma seguente confronta l'iterazione su a rangee 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)

I risultati seguenti mostrano che xrangeè davvero più veloce, ma non abbastanza per sudare.

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

Quindi, certamente xrange, usa , ma a meno che tu non abbia un hardware limitato, non preoccuparti troppo.


Il tuo list_lennon viene utilizzato e quindi stai eseguendo questo codice solo per elenchi di lunghezza 100.
Segna il

Consiglio di modificare effettivamente la lunghezza dell'elenco:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Segna il

Wow, questo si preannuncia una buona settimana, grazie, risolto. Tuttavia, non è una grande differenza.
speedplane il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.