¿Por qué no hay una función xrange en Python3?
Recientemente comencé a usar Python3 y su falta xrange
duele.
Ejemplo sencillo:
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()
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()
Los resultados son, respectivamente:
1.53888392448
3.215819835662842
¿Porqué es eso? Quiero decir, ¿por qué xrange
se ha eliminado? Es una gran herramienta para aprender. Para los principiantes, como yo, como lo fuimos todos en algún momento. ¿Por qué eliminarlo? ¿Alguien puede indicarme el PEP adecuado? No puedo encontrarlo.
Algunas medidas de rendimiento, usando timeit
en lugar de intentar hacerlo manualmente con time
.
Primero, Apple 2.7.2 de 64 bits:
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
Ahora, python.org 3.3.0 de 64 bits:
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
Aparentemente, 3.x range
es realmente un poco más lento que xrange
2.x. Y la xrange
función del OP no tiene nada que ver con eso. (No es sorprendente, ya que no es probable que una llamada única a la __iter__
ranura sea visible entre 10000000 de llamadas a cualquier cosa que suceda en el bucle, pero alguien mencionó esta posibilidad).
Pero es sólo un 30% más lento. ¿Cómo es que el OP se volvió 2 veces más lento? Bueno, si repito las mismas pruebas con Python de 32 bits, obtengo 1,58 frente a 3,12. Así que supongo que este es otro de esos casos en los que 3.x se ha optimizado para el rendimiento de 64 bits de maneras que perjudican a los de 32 bits.
pero, realmente importa? Mira esto, con 3.3.0 de 64 bits nuevamente:
In [86]: %timeit [x for x in range(10000000) if x%4 == 0]
1 loops, best of 3: 3.65 s per loop
Por lo tanto, construirlo list
lleva más del doble de tiempo que toda la iteración.
Y en cuanto a "consume muchos más recursos que Python 2.6+", según mis pruebas, parece que un 3.x range
tiene exactamente el mismo tamaño que un 2.x xrange
y, incluso si fuera 10 veces más grande, crearía la lista innecesaria. sigue siendo un problema 10000000 veces mayor que cualquier cosa que la iteración del rango pueda hacer.
¿Y qué pasa con un for
bucle explícito en lugar del bucle C 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
Por lo tanto, se pierde casi tanto tiempo en la for
declaración como en el trabajo real de iterar el archivo range
.
Si le preocupa optimizar la iteración de un objeto de rango, probablemente esté buscando en el lugar equivocado.
Mientras tanto, sigues preguntando por qué xrange
se eliminó, sin importar cuántas veces la gente te diga lo mismo, pero lo repetiré nuevamente: no se eliminó: se le cambió el nombre a range
, y la versión 2.x range
es lo que se eliminó.
Aquí hay alguna prueba de que el range
objeto 3.3 es un descendiente directo del xrange
objeto 2.x (y no de la range
función 2.x): la fuente de 3.3range
y 2.7xrange
. Incluso puede ver el historial de cambios (vinculado, creo, al cambio que reemplazó la última instancia de la cadena "xrange" en cualquier parte del archivo).
Entonces, ¿por qué es más lento?
Bueno, por un lado, han agregado muchas características nuevas. Por otro lado, han realizado todo tipo de cambios en todas partes (especialmente dentro de la iteración) que tienen efectos secundarios menores. Y se ha trabajado mucho para optimizar drásticamente varios casos importantes, incluso si a veces pesimiza ligeramente los casos menos importantes. Sume todo esto y no me sorprende que iterar lo range
más rápido posible ahora sea un poco más lento. Es uno de esos casos menos importantes en los que a nadie le importaría lo suficiente como para centrarse. Es probable que nadie tenga un caso de uso en la vida real en el que esta diferencia de rendimiento sea el punto crítico de su código.
El rango de Python3 es el rango x de Python2. No es necesario envolverlo con un iter. Para obtener una lista real en Python3, debe usarlist(range(...))
Si quieres algo que funcione con Python2 y Python3, prueba esto
try:
xrange
except NameError:
xrange = range
El tipo de Python 3 range
funciona igual que el de Python 2 xrange
. No estoy seguro de por qué estás viendo una desaceleración, ya que el iterador devuelto por tu xrange
función es exactamente lo que obtendrías si iteraras range
directamente.
No puedo reproducir la desaceleración en mi sistema. Así es como lo probé:
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, range
es un poquito más rápido:
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
Recientemente aprendí que range
el tipo de Python 3 tiene otras características interesantes, como la compatibilidad con el corte: range(10,100,2)[5:25:5]
is range(20, 60, 10)
!
Una forma de arreglar su código python2 es:
import sys
if sys.version_info >= (3, 0):
def xrange(*args, **kwargs):
return iter(range(*args, **kwargs))