Perché non esiste alcuna funzione xrange in Python3?


273

Di recente ho iniziato a usare Python3 ed è la mancanza di xrange fa male.

Esempio semplice:

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

I risultati sono, rispettivamente:

1) 1.53888392448 2) 3.215819835662842

Perché? Voglio dire, perché xrange è stato rimosso? È un ottimo strumento per imparare. Per i principianti, proprio come me, come se fossimo tutti ad un certo punto. Perché rimuoverlo? Qualcuno può indicarmi il PEP corretto, non riesco a trovarlo.

Saluti.


231
rangein Python 3.x xrangeproviene da Python 2.x. Fu infatti rangerimosso Python 2.x.
Anorov,

27
PS, non dovresti mai stare con time. Oltre ad essere più facile da usare e più difficile da sbagliare, e ripetere i test per te, si timeitprende cura di tutti i tipi di cose che non ricorderai, o anche di come, di cui occuparti (come disabilitare il GC) e che potresti usare un orologio con risoluzione migliaia di volte migliore.
Abarnert,

7
Inoltre, perché stai testando il tempo per filtrare l' rangeattivazione x%4 == 0? Perché non solo test list(xrange())vs. list(range()), quindi c'è meno lavoro estraneo possibile? (Ad esempio, come fai a sapere 3.x non sta andando x%4più lentamente?) Del listresto , perché stai costruendo un enorme , che comporta molta allocazione di memoria (che, oltre ad essere lento, è anche incredibilmente variabile) ?
Abarnert,

5
Vedi docs.python.org/3.0/whatsnew/3.0.html , sezione "Visualizzazioni e iteratori anziché elenchi": "range () ora si comporta come xrange () usato per comportarsi, tranne per il fatto che funziona con valori di dimensioni arbitrarie. non esiste più." Quindi, range ora restituisce un iteratore. iter(range)è ridondante.
ToolmakerSteve

9
Siamo spiacenti, realizzato citando il documento di modifica non lo rende accecantemente evidente. Per chiunque sia confuso e non voglia leggere la risposta a lungo accettata e tutti i suoi commenti: Ovunque stavi usando xrange in python 2, usa range in python 3. Fa quello che faceva una volta xrange, che è restituisce un iteratore. Se hai bisogno dei risultati in un elenco, fallo list(range(..)). Ciò equivale alla gamma di Python 2. O per dirlo in un altro modo: xrange è stato rinominato range, perché è il valore predefinito migliore; non era necessario avere entrambi, fare list(range)se si ha davvero bisogno di un elenco. .
ToolmakerSteve

Risposte:


175

Alcune misurazioni delle prestazioni, usando timeitinvece di provare a farlo manualmente con time.

Innanzitutto, Apple 2.7.2 a 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

Ora, python.org 3.3.0 a 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

A quanto pare, 3.x rangeè davvero un po 'più lento di 2.x xrange. E la xrangefunzione del PO non ha nulla a che fare con esso. (Non sorprende, dal momento che una chiamata una tantum allo __iter__slot non è probabilmente visibile tra 10000000 chiamate a qualsiasi cosa accada nel loop, ma qualcuno lo ha sollevato come una possibilità.)

Ma è solo il 30% più lento. In che modo l'OP ha ottenuto 2x più lento? Bene, se ripeto gli stessi test con Python a 32 bit, ottengo 1,58 contro 3,12. Quindi la mia ipotesi è che questo è ancora un altro di quei casi in cui 3.x è stato ottimizzato per le prestazioni a 64 bit in modo da danneggiare 32-bit.

Ma importa davvero? Dai un'occhiata, con 3.3.0 di nuovo a 64 bit:

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

Quindi, la costruzione di listrichiede più del doppio dell'intera iterazione.

E per quanto riguarda "consuma molte più risorse di Python 2.6+", dai miei test, sembra che una 3.x rangeabbia esattamente le stesse dimensioni di una 2.x xrange- e, anche se fosse 10x più grande, costruendo l'elenco non necessario è ancora circa 10000000 volte più un problema di qualsiasi altra cosa possa fare l'iterazione dell'intervallo.

E che dire di un forciclo esplicito anziché del ciclo C all'interno 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

Quindi, quasi tutto il tempo perso fornell'affermazione come nel lavoro effettivo di iterazione di range.

Se sei preoccupato di ottimizzare l'iterazione di un oggetto range, probabilmente stai cercando nel posto sbagliato.


Nel frattempo, continui a chiederti perché è xrangestato rimosso, non importa quante volte le persone ti dicono la stessa cosa, ma lo ripeterò di nuovo: non è stato rimosso: è stato rinominato rangee la 2.x rangeè ciò che è stato rimosso.

Ecco alcune prove del fatto che l' rangeoggetto 3.3 è un discendente diretto xrangedell'oggetto 2.x (e non della rangefunzione 2.x ): l'origine a 3.3range e 2.7xrange . Puoi persino vedere la cronologia delle modifiche (collegata, credo, alla modifica che ha sostituito l'ultima istanza della stringa "xrange" in qualsiasi punto del file).

Quindi, perché è più lento?

Bene, per uno, hanno aggiunto molte nuove funzionalità. Per un altro, hanno fatto tutti i tipi di cambiamenti in tutto il luogo (specialmente all'interno dell'iterazione) che hanno effetti collaterali minori. E c'era stato un sacco di lavoro per ottimizzare drasticamente vari casi importanti, anche se a volte pessimizza leggermente i casi meno importanti. Aggiungilo tutto e non mi sorprende che iterare il rangepiù velocemente possibile sia ora un po 'più lento. È uno di quei casi meno importanti su cui nessuno si preoccuperebbe mai abbastanza di concentrarsi. Nessuno probabilmente avrà mai un caso d'uso reale in cui questa differenza di prestazioni è l'hotspot nel proprio codice.


Ma è solo il 30% più lento. Ancora più lento, ma un ottimo compagno di risposta, qualcosa a cui pensare. Non risponde alla mia domanda però: perché è stato rimosso xrange ?? Pensaci in questo modo: se avessi un'app dipendente dalle prestazioni basata sul multiprocessing sapendo quante code devi consumare una volta, il 30% farebbe la differenza o no? Vedi, dici che non importa, ma ogni volta che uso il range sento l'enorme suono angosciante della ventola che significa che la cpu è la peggiore, mentre xrange non lo fa. Pensaci;)
Catalogna,

9
@catalesia: Ancora una volta, non è stato rimosso, è stato semplicemente rinominato range. L' rangeoggetto in 3.3 è un diretto discendente xrangedell'oggetto in 2.7, non della rangefunzione in 2.7. È come chiedere mentre è itertools.imapstato rimosso a favore di map. Non c'è risposta, perché non è successo niente del genere.
Abarnert,

1
@catalesia: i piccoli cambiamenti nelle prestazioni non sono presumibilmente il risultato di una decisione di progettazione diretta per rendere le gamme più lente, ma un effetto collaterale di 4 anni di modifiche in tutto Python che hanno reso molte cose più veloci, alcune cose un po 'più lente (e alcune cose più veloce su x86_64 ma più lento su x86, o più veloce in alcuni casi d'uso ma più lento in altri, ecc.). Nessuno era probabilmente preoccupato per una differenza del 30% in entrambi i casi in quanto tempo ci vuole per iterare un rangepo 'senza fare altro.
Abarnert,

1
"Nessuno era probabilmente preoccupato per una differenza del 30% in entrambi i casi in quanto tempo impiega a scorrere un intervallo senza fare nient'altro. " Esatto.
Catalogna,

18
@catalesia: Sì, esattamente. Ma sembra che tu pensi che significhi l'opposto di ciò che dice. Non è un caso d'uso a cui nessuno si preoccuperà mai, quindi nessuno ha notato che è rallentato del 30%. E allora? Se riesci a trovare un programma di vita reale che funziona più lentamente in Python 3.3 rispetto a 2.7 (o 2.6) per questo motivo, alla gente interesserà. Se non puoi, non lo faranno, e neanche tu dovresti.
Abarnert,

141

Gamma di python3 è xrange di python2. Non è necessario avvolgerlo attorno. Per ottenere un elenco effettivo in Python3, è necessario utilizzarelist(range(...))

Se vuoi qualcosa che funzioni con Python2 e Python3, prova questo

try:
    xrange
except NameError:
    xrange = range

1
A volte è necessario un codice che funzioni sia in Python 2 che in 3. Questa è una buona soluzione.
Greg Glockner,

3
Il problema è che con questo, il codice che utilizza entrambi rangee xrangesi comporterà in modo diverso. Non è abbastanza per fare questo, si dovrebbe anche assicurarsi di non dare per scontato che rangestia restituendo un elenco (come farebbe in Python 2).
LangeHaare,

Puoi usare xrange da questo progetto. C'è uno futurizestrumento per convertire automaticamente il codice sorgente: python-future.org/…
guettli

17

Il rangetipo di Python 3 funziona esattamente come quello di Python 2 xrange. Non sono sicuro del motivo per cui stai vedendo un rallentamento, poiché l'iteratore restituito dalla tua xrangefunzione è esattamente quello che otterrai se eseguissi l'iterazione rangedirettamente.

Non riesco a riprodurre il rallentamento del mio sistema. Ecco come ho testato:

Python 2, con 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, con rangeè un po 'più veloce:

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

Di recente ho appreso che il rangetipo di Python 3 ha alcune altre funzioni pulite, come il supporto per lo slicing: range(10,100,2)[5:25:5]is range(20, 60, 10)!


Forse il rallentamento viene dalla ricerca del nuovo xrangecosì tante volte, o è fatto solo una volta?
askewchan,

Un iteratore aumenta effettivamente la velocità comunque? Ho pensato che avesse appena salvato memoria.
askewchan,

3
@catalesia Penso che il punto qui sia che non è xrangestato rimosso, solo rinominato .
askewchan,

1
@Blckknght: Saluti, ma fa ancora schifo avere una spiegazione del tipo: "Set letterali e comprensioni [19] [20] [done] {x} significa set ([x]); {x, y} significa set ([ x, y]). {F (x) per x in S se P (x)} significa set (F (x) per x in S se P (x)). NB. {range (x)} significa set ( [range (x)]), NOT set (range (x)). Non c'è valore letterale per un set vuoto; usa set () (o {1} & {2} :-). Non c'è frozenset letterale; sono anche raramente necessario. "
Catalogna,

3
La vittoria più grande in 3.x range, per quanto mi riguarda, è il tempo costante __contains__. I neofiti scrivevano 300000 in xrange(1000000)e questo faceva sì che iterasse il tutto xrange(o almeno il primo 30%), quindi dovevamo spiegare perché quella era una cattiva idea, anche se sembra così pitonica. Ora è pitonico.
Abarnert,

1

Un modo per sistemare il tuo codice python2 è:

import sys

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

1
Il punto è in python3 xrange non definito, quindi il codice legacy che utilizzava le interruzioni xrange.
Andrew Pate,

no, definisci semplicemente range = xrangecome è commentato da @John La Roy
mimi.vx il

@ mimi.vx Non sono sicuro che range = xrange funzionerebbe in Python3 perché xrange non è definito. Il mio commento si riferisce al caso in cui hai un vecchio codice legacy che contiene chiamate xrange E il tuo tentativo di farlo funzionare su python3.
Andrew Pate,

1
Ah, mio ​​cattivo ... xrange = range... ho scambiato le dichiarazioni
mimi.vx,

range È un iiteratore, e comunque sarebbe un'idea terribile anche se non lo fosse, perché deve prima decomprimere l'intera gamma e perde i vantaggi dell'utilizzo di un iteratore per questo genere di cose. Quindi la risposta corretta non è "range = xrange" è "xrange = range"
Shayne,

0

xrange di Python 2 è un generatore e implementa iteratore mentre range è solo una funzione. In Python3 non so perché sia ​​stato abbandonato dalla gamma xrange.


No, l'intervallo non è un interatore. Non puoi fare il prossimo () con questa struttura. Per ulteriori informazioni, puoi consultare qui treyhunner.com/2018/02/python-range-is-not-an-iterator
Michel Fernandes

Grazie mille per il chiarimento. Ma riaffermerò l'intento del commento originale, e cioè che PY3 range()è l'equivalente di PY2 xrange(). E quindi in PY3 xrange()è ridondante.
Stephen Rauch,

-2

comp: ~ $ python Python 2.7.6 (impostazione predefinita, 22 giu 2015, 17:58:13) [GCC 4.8.2] su 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

Con timeit number = 1 param:

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

,2245171070098877

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

,10750913619995117

comp: ~ $ python3 Python 3.4.3 (impostazione predefinita, 14 ottobre 2015, 20:28:29) [GCC 4.8.4] su 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

Con timeit number = 1,2,3,4 param funziona in modo rapido e lineare:

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

,09329321900440846

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

,18501482300052885

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

,2703447980020428

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

,36209142999723554

Quindi sembra che misuriamo 1 ciclo di ciclo in esecuzione come timeit.timeit ("[x per x nell'intervallo (1000000) se x% 4]", numero = 1) (come usiamo realmente nel codice reale) python3 funziona abbastanza velocemente, ma in cicli ripetuti python 2 xrange () vince in velocità contro range () da python 3.


ma questo è dal linguaggio stesso ... niente a che fare con xrange / range.
mimi.vx,
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.