ti-enxame.com

Por que não há função xrange no Python3?

Recentemente eu comecei a usar o Python3 e a falta de xrange dói.

Exemplo simples:

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

Os resultados são, respectivamente:

1) 1,53888392448 2) 3,215819835662842

Por que é que? Por que o xrange foi removido? É uma ótima ferramenta para aprender. Para os iniciantes, assim como eu, como todos nós estávamos em algum momento. Por que remover isso? Alguém pode me indicar o PEP adequado, não consigo encontrá-lo.

Felicidades.

232
catalesia

Algumas medidas de desempenho, usando timeit em vez de tentar fazê-lo manualmente com time.

Primeiro, a 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

Agora, python.org 3.3.0 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 é realmente um pouco mais lento que 2.x xrange. E a função xrange do OP não tem nada a ver com isso. (Não é de surpreender, já que uma chamada única para o slot __iter__ provavelmente não estará visível entre 10000000 chamadas para o que quer que aconteça no loop, mas alguém mencionou isso como uma possibilidade.)

Mas é apenas 30% mais lento. Como o OP ficou 2x mais lento? Bem, se eu repetir os mesmos testes com Python de 32 bits, fico com 1,58 contra 3,12. Então, meu palpite é que esse é mais um daqueles casos em que o 3.x foi otimizado para desempenho de 64 bits de maneiras que prejudicam o 32-bit.

Mas isso realmente importa? Verifique isso, com 3.3.0 de 64 bits novamente:

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

Então, construir o list leva mais do que o dobro do tempo de toda a iteração. 

E quanto a "consome muito mais recursos do que o Python 2.6+", dos meus testes, parece que um 3.x range é exatamente do mesmo tamanho que um 2.x xrange - e, mesmo que fosse 10x maior, construindo o lista desnecessária ainda é cerca de 10000000x mais de um problema do que qualquer coisa que a iteração de intervalo poderia fazer.

E que tal um loop for explícito em vez do loop C dentro de 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

Portanto, quase tanto tempo desperdiçado na instrução for quanto no trabalho real de iterar o range.

Se você está preocupado em otimizar a iteração de um objeto de intervalo, provavelmente está procurando no lugar errado.


Enquanto isso, você continua perguntando por que xrange foi removido, não importa quantas vezes as pessoas digam a mesma coisa, mas eu repetirei novamente: Ele não foi removido: foi renomeado para range e o 2.x range é o que foi removido.

Aqui está uma prova de que o objeto 3.3 range é um descendente direto do objeto 2.x xrange (e não da função 2.x range): a origem para 3.3 range e 2.7 xrange . Você pode até ver o histórico de alterações (ligado, acredito, a mudança que substituiu a última instância da string "xrange" em qualquer lugar no arquivo).

Então, por que é mais lento?

Bem, por um lado, eles adicionaram muitos novos recursos. Por outro lado, eles fizeram todos os tipos de mudanças em todo o lugar (especialmente dentro da iteração) que têm efeitos colaterais menores. E houve muito trabalho para otimizar dramaticamente vários casos importantes, mesmo que algumas vezes pessimize ligeiramente casos menos importantes. Adicione tudo isso, e não estou surpreso que iterar um range o mais rápido possível agora seja um pouco mais lento. É um daqueles casos menos importantes que ninguém se importaria o suficiente para se concentrar. É provável que ninguém tenha um caso de uso da vida real em que essa diferença de desempenho seja o ponto de acesso em seu código.

155
abarnert

O intervalo do Python3 é xrange do Python2. Não há necessidade de envolver um iter em torno dele. Para obter uma lista real no Python3, você precisa usar list(range(...))

Se você quiser algo que funcione com Python2 e Python3, tente

try:
    xrange
except NameError:
    xrange = range
129
John La Rooy

O tipo range do Python 3 funciona exatamente como o xrange do Python 2. Não sei por que você está vendo uma lentidão, já que o iterador retornado pela sua função xrange é exatamente o que você obteria se fosse iterado diretamente sobre range.

Não consigo reproduzir a lentidão no meu sistema. Aqui está como eu testei:

Python 2, com 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

O Python 3, com range, é um pouco mais 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

Recentemente aprendi que o tipo range do Python 3 tem alguns outros recursos interessantes, como suporte para fatiar: range(10,100,2)[5:25:5] é range(15, 60, 10)!

14
Blckknght

o xrange do Python 2 é um gerador e implementa o iterador, enquanto o intervalo é apenas uma função. No Python3, não sei por que foi deixado o xrange.

0
Michel Fernandes