¿Deberías siempre favorecer xrange () sobre range ()?

460

¿Por qué o por qué no?

mbac32768
fuente
36
¿Alguien puede describir brevemente la diferencia entre los 2 para nosotros los que no somos python? Tal vez algo como "xrange () hace todo rango (), pero también es compatible con X, Y y Z"
Outlaw Programmer
87
range (n) crea una lista que contiene todos los enteros 0..n-1. Esto es un problema si haces rango (1000000), porque terminarás con una lista> 4Mb. xrange se ocupa de esto devolviendo un objeto que pretende ser una lista, pero que solo resuelve el número necesario del índice solicitado y lo devuelve.
Brian
44
Ver stackoverflow.com/questions/94935
James McMahon el
44
Básicamente, mientras que range(1000)es a list, xrange(1000)es un objeto que actúa como a generator(aunque ciertamente no lo es ). Además, xrangees más rápido. Puede import timeit from timeity luego hacer un método que solo tenga for i in xrange: passy otro para range, luego hacer timeit(method1)y timeit(method2)y, he aquí, xrange es casi el doble de rápido a veces (es decir, cuando no necesita una lista). (Para mí, para i in xrange(1000):passvs para i in range(1000):passtomó 13.316725969314575vs 21.190124988555908segundos respectivamente, eso es mucho.)
dylnmc
Otra prueba de rendimiento da xrange(100)como 20% más rápido que range(100).
Evgeni Sergeev

Respuestas:

443

Para el rendimiento, especialmente cuando está iterando en un amplio rango, xrange()generalmente es mejor. Sin embargo, todavía hay algunos casos por los que podría preferir range():

  • En python 3, range()hace lo que xrange()solía hacer y xrange()no existe. Si desea escribir código que se ejecutará en Python 2 y Python 3, no puede usarlo xrange().

  • range()en realidad puede ser más rápido en algunos casos, por ejemplo. si itera sobre la misma secuencia varias veces. xrange()tiene que reconstruir el objeto entero cada vez, pero range()tendrá objetos enteros reales. (Sin embargo, siempre funcionará peor en términos de memoria)

  • xrange()no es utilizable en todos los casos donde se necesita una lista real. Por ejemplo, no admite cortes, ni ningún método de lista.

[Editar] Hay un par de publicaciones que mencionan cómo range()se actualizará con la herramienta 2to3. Para el registro, aquí está el resultado de ejecutar la herramienta en algunos usos de muestra de range()yxrange()

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)

Como puede ver, cuando se usa en un bucle for o comprensión, o cuando ya está envuelto con list (), el rango no se modifica.

Brian
fuente
55
¿Qué quieres decir con "el rango se convertirá en un iterador"? ¿No debería ser esto "generador"?
Michael Mior
44
No. Generator se refiere a un tipo específico de iterador, y el nuevo rangeno es un iterador de todos modos.
user2357112 es compatible con Monica
Tu segunda bala realmente no tiene ningún sentido. Estás diciendo que no puedes usar un objeto varias veces; ¡Seguro que puede! intente xr = xrange(1,11)luego en la siguiente línea for i in xr: print " ".join(format(i*j,"3d") for j in xr)y listo! Tienes tus horarios hasta diez. Funciona igual que r = range(1,11)y for i in r: print " ".join(format(i*j,"3d") for j in r)... todo es un objeto en Python2. Creo que lo que querías decir es que puedes hacer una comprensión basada en índices (si eso tiene sentido) mejor rangeen lugar de hacerlo xrange. El rango es útil muy raramente, creo
dylnmc
(Habitación insuficiente) Aunque, yo hago piensan que rangepuede ser útil si desea utilizar una listen un bucle y luego cambiar ciertos índices basados en ciertas condiciones o cosas Anexar a esa lista, entonces rangees definitivamente mejor. Sin embargo, xrangees simplemente más rápido y usa menos memoria, por lo que para la mayoría de las aplicaciones de bucle, parece ser el mejor. Hay casos, volviendo a la pregunta del interrogador, que rara vez existen, donde rangesería mejor. Quizás no tan raramente como estoy pensando, pero ciertamente uso el xrange95% del tiempo.
dylnmc
129

No, ambos tienen sus usos:

Úselo xrange()al iterar, ya que ahorra memoria. Decir:

for x in xrange(1, one_zillion):

más bien que:

for x in range(1, one_zillion):

Por otro lado, úsalo range()si realmente quieres una lista de números.

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

Usted debe favorecer range()más xrange()sólo cuando se necesita una lista real. Por ejemplo, cuando desea modificar la lista devuelta por range(), o cuando desea dividirla. Para la iteración o incluso la indexación normal, xrange()funcionará bien (y generalmente es mucho más eficiente). Hay un punto donde range()es un poco más rápido que xrange()para listas muy pequeñas, pero dependiendo de su hardware y otros detalles, el punto de equilibrio puede ser el resultado de la longitud 1 o 2; No es algo de qué preocuparse. Prefiero xrange().

Thomas Wouters
fuente
30

Otra diferencia es que xrange () no puede admitir números mayores que C ints, por lo que si desea tener un rango utilizando el soporte de números grandes incorporado de Python, debe usar 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 no tiene este 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)
Brian Minton
fuente
13

xrange()es más eficiente porque en lugar de generar una lista de objetos, solo genera un objeto a la vez. En lugar de 100 enteros, y todos sus gastos generales, y la lista para colocarlos, solo tiene un entero a la vez. Generación más rápida, mejor uso de memoria, código más eficiente.

A menos que necesite específicamente una lista para algo, siempre estoy a favor xrange()

Dan Udey
fuente
8

range () devuelve una lista, xrange () devuelve un objeto xrange.

xrange () es un poco más rápido y un poco más eficiente en memoria. Pero la ganancia no es muy grande.

La memoria extra utilizada por una lista, por supuesto, no solo se desperdicia, las listas tienen más funcionalidad (división, repetición, inserción, ...). Las diferencias exactas se pueden encontrar en la documentación . No hay una regla estricta, use lo que se necesita.

Python 3.0 todavía está en desarrollo, pero IIRC range () será muy similar a xrange () de 2.X y list (range ()) puede usarse para generar listas.

jschultz
fuente
5

Solo me gustaría decir que REALMENTE no es tan difícil obtener un objeto xrange con funcionalidad de corte e indexación. He escrito un código que funciona bastante bien y es tan rápido como xrange para cuando cuenta (iteraciones).

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)

Honestamente, creo que todo el asunto es un poco tonto y xrange debería hacer todo esto de todos modos ...

Garrett Berg
fuente
Sí de acuerdo; desde una tecnología totalmente diferente, verifique el trabajo en lodash para hacerlo perezoso: github.com/lodash/lodash/issues/274 . Rebanar, etc., aún debe ser lo más flojo posible y, cuando no, solo reificar.
Rob Grant
4

Un buen ejemplo dado en el libro: Practical Python Por Magnus Lie Hetland

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

No recomendaría usar range en lugar de xrange en el ejemplo anterior, aunque solo se necesitan los primeros cinco números, range calcula todos los números, y eso puede llevar mucho tiempo. Con xrange, esto no es un problema porque calcula solo los números necesarios.

Sí, leí la respuesta de @ Brian: en python 3, range () es un generador de todos modos y xrange () no existe.

Grijesh Chauhan
fuente
3

Ir con rango por estas razones:

1) xrange desaparecerá en las nuevas versiones de Python. Esto le brinda compatibilidad futura fácil.

2) el rango tomará las eficiencias asociadas con xrange.

torial
fuente
13
No hagas esto. xrange () desaparecerá, pero también muchas otras cosas. La herramienta que usará para traducir su código Python 2.x al código Python 3.x traducirá automáticamente xrange () a range (), pero range () se traducirá a la lista menos eficiente (range ()).
Thomas Wouters el
10
Thomas: En realidad es un poco más inteligente que eso. Traducirá range () en situaciones en las que claramente no necesita una lista real (por ejemplo, en un bucle for, o comprensión) a simplemente range (). Solo los casos en los que se asigna a una variable o se usa directamente se deben envolver con list ()
Brian
2

De acuerdo, todos aquí tienen una opinión diferente sobre las ventajas y desventajas de xrange versus range. En su mayoría son correctos, xrange es un iterador, y el rango se desarrolla y crea una lista real. Para la mayoría de los casos, realmente no notará una diferencia entre los dos. (Puede usar el mapa con rango pero no con xrange, pero usa más memoria).

Sin embargo, lo que creo que quieres escuchar es que la opción preferida es xrange. Dado que range en Python 3 es un iterador, la herramienta de conversión de código 2to3 convertirá correctamente todos los usos de xrange en range, y arrojará un error o advertencia para los usos de range. Si desea asegurarse de convertir fácilmente su código en el futuro, usará solo xrange y list (xrange) cuando esté seguro de que desea una lista. Aprendí esto durante el sprint CPython en PyCon este año (2008) en Chicago.

Douglas Mayle
fuente
8
Eso no es cierto. El código como "para x en el rango (20)" se dejará como rango, y el código como "x = rango (20)" se convertirá en "x = lista (rango (20))" - sin errores. Además, si desea escribir código que se ejecutará tanto en 2.6 como en 3.0, range () es su única opción sin agregar funciones de compatibilidad.
Brian
2
  • range(): range(1, 10)devuelve una lista de 1 a 10 números y mantiene toda la lista en la memoria.
  • xrange(): Me gusta range(), pero en lugar de devolver una lista, devuelve un objeto que genera los números en el rango bajo demanda. Para el bucle, esto es ligeramente más rápido range()y más eficiente en memoria. xrange()objeto como un iterador y genera los números a pedido (evaluación diferida).
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()hace lo mismo que xrange()solía hacer en Python 3 y no existe ningún término xrange()en Python 3. en range()realidad puede ser más rápido en algún escenario si itera sobre la misma secuencia varias veces. xrange()tiene que reconstruir el objeto entero cada vez, pero range()tendrá objetos enteros reales.

Tushar.PUCSD
fuente
2

Si bien xrangees más rápido que rangeen la mayoría de las circunstancias, la diferencia en el rendimiento es bastante mínima. El pequeño programa a continuación compara la iteración sobre ay rangean 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)

Los resultados a continuación muestran que, de xrangehecho, es más rápido, pero no lo suficiente como para sudar.

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

Así que, por supuesto xrange, use , pero a menos que esté en un hardware restringido, no se preocupe demasiado por eso.

Speedplane
fuente
No list_lense está utilizando y, por lo tanto, solo está ejecutando este código para listas de longitud 100.
Marque el
En realidad, recomendaría modificar la duración de la lista:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Mark
Wow, esto se perfila como una buena semana, gracias, arreglado. Aún así, no es una gran diferencia.
Speedplane