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

273

Recentemente eu comecei a usar 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? Quero dizer, por que 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 removê-lo? Alguém pode me indicar o PEP adequado, não consigo encontrá-lo.

Felicidades.

catalesia
fonte
231
rangeno Python 3.x é xrangedo Python 2.x. Na verdade, foi o Python 2.x rangeque foi removido.
Anorov
27
PS, você nunca deve ter tempo time. Além de ser mais fácil de usar e mais difícil de errar, e repetir testes para você, timeitcuida de todos os tipos de coisas que você não se lembrará, ou mesmo saberá, como cuidar (como desabilitar o GC) e pode usar um relógio com milhares de vezes melhor resolução.
abarnert
7
Além disso, por que você está testando o tempo para filtrar o rangerecurso x%4 == 0? Por que não apenas testar list(xrange())vs. list(range()), para que haja o mínimo de trabalho estranho possível? (Por exemplo, como você sabe que o 3.x não está indo x%4mais devagar?) Por esse motivo, por que você está construindo uma enorme list, que envolve muita alocação de memória (que, além de lenta, também é incrivelmente variável) ?
abarnert
5
Consulte docs.python.org/3.0/whatsnew/3.0.html , seção "Exibições e iteradores em vez de listas": "range () agora se comporta como xrange () costumava se comportar, exceto que funciona com valores de tamanho arbitrário. não existe mais." Portanto, o intervalo agora retorna um iterador. iter(range)é redundante.
Home
9
Desculpe, percebi que citar o documento de alteração não o torna cegamente óbvio. Para qualquer pessoa que esteja confusa e que não queira ler a resposta aceita há muito tempo e todos os seus comentários: Onde quer que você estivesse usando o xrange no python 2, use range no python 3. Ele faz o que o xrange costumava fazer, o que é retorne um iterador. Se você precisar dos resultados em uma lista, faça list(range(..)). Isso é equivalente ao alcance do python 2. Ou, de outra forma: xrange foi renomeado como range, porque é o melhor padrão; não era necessário ter os dois, faça list(range)se você realmente precisa de uma lista. .
Página

Respostas:

175

Algumas medições de desempenho, usando em timeitvez de tentar fazê-lo manualmente time.

Primeiro, o 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 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 rangerealmente é um pouco mais lento do que 2.x xrange. E a xrangefunção do OP não tem nada a ver com isso. (Não é surpresa, uma vez que uma chamada única para o __iter__slot provavelmente não será visível entre 10000000 chamadas para o que acontecer no loop, mas alguém a abordou como uma possibilidade.)

Mas é apenas 30% mais lento. Como o OP ficou 2x mais lento? Bem, se eu repetir os mesmos testes com o Python de 32 bits, recebo 1,58 vs. 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 32 bits.

Mas isso realmente importa? Verifique isso com o 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

Portanto, a construção do listleva mais que o dobro do tempo da iteração inteira.

E quanto a "consome muito mais recursos que o Python 2.6+", nos meus testes, parece que um 3.x rangeé exatamente do mesmo tamanho que um 2.x xrange- e, mesmo que fosse 10 vezes maior, cria a lista desnecessária ainda é 10000000x mais problemático do que qualquer coisa que a iteração de alcance possa fazer.

E o que dizer de um forloop explícito em vez do loop C dentro 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 perdido na fordeclaração 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 o lugar errado.


Enquanto isso, você continua perguntando por que xrangefoi removido, não importa quantas vezes as pessoas lhe digam a mesma coisa, mas vou repetir novamente: Não foi removido: foi renomeado para rangee o 2.x rangefoi removido.

Aqui estão algumas provas de que o rangeobjeto 3.3 é um descendente direto do xrangeobjeto 2.x (e não da rangefunção 2.x ): a origem para 3.3range e 2.7xrange . Você pode até ver o histórico de alterações (vinculado a, acredito, a alteração que substituiu a última instância da string "xrange" em qualquer lugar do arquivo).

Então, por que é mais lento?

Bem, por um lado, eles adicionaram muitos recursos novos. Por outro lado, eles fizeram todos os tipos de alterações em todo o lugar (especialmente na iteração interna) que têm efeitos colaterais menores. E havia muito trabalho para otimizar dramaticamente vários casos importantes, mesmo que às vezes pessimizasse levemente casos menos importantes. Adicione tudo isso, e não estou surpreso que iterar o rangemais rápido possível seja agora um pouco mais lento. É um daqueles casos menos importantes que ninguém jamais se importaria o suficiente para focar. É 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.

abarnert
fonte
Mas é apenas 30% mais lento. Ainda mais lento, mas uma ótima resposta, algo para se pensar. Porém, ele não responde à minha pergunta: por que o xrange foi removido? Pense da seguinte maneira: se você tivesse um aplicativo dependente de desempenho baseado em multiprocessamento, sabendo quanta fila precisa consumir por vez, 30% faria diferença ou não? Veja bem, você diz que isso não importa, mas toda vez que eu uso o range, ouço aquele som enorme e angustiante do ventilador, o que significa que a CPU está ligada é pior, enquanto o xrange não faz isso. Pense em algo;)
catalesia
9
@catalesia: Mais uma vez, não foi removido, apenas foi renomeado range. O rangeobjeto em 3.3 é um descendente direto do xrangeobjeto em 2.7, não da rangefunção em 2.7. É como perguntar enquanto itertools.imapfoi removido em favor de map. Não há resposta, porque nada disso aconteceu.
abarnert
1
@catalesia: Presumivelmente, as pequenas alterações no desempenho não são o resultado de uma decisão direta do projeto de tornar os intervalos mais lentos, mas um efeito colateral de 4 anos de alterações em todo o Python que tornaram muitas coisas mais rápidas, algumas um pouco mais lentas (e outras mais rápido em x86_64, mas mais lento em x86, ou mais rápido em alguns casos de uso, mas mais lento em outros, etc.). Provavelmente, ninguém estava preocupado com uma diferença de 30% no tempo que leva para repetir um rangetempo sem fazer mais nada.
abarnert
1
"Provavelmente ninguém estava preocupado com uma diferença de 30% no tempo que leva para percorrer um intervalo sem fazer mais nada. " Exatamente.
catalesia
18
@ catalatalesia: Sim, exatamente. Mas você parece pensar que isso significa o oposto do que diz. Não é um caso de uso com o qual alguém se preocupe, então ninguém notou que ficou 30% mais lento. E daí? Se você puder encontrar um programa da vida real que seja executado mais lentamente no Python 3.3 do que no 2.7 (ou 2.6) por causa disso, as pessoas se importarão. Se você não puder, eles não o farão, e você também não deveria.
abarnert
141

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

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

try:
    xrange
except NameError:
    xrange = range
John La Rooy
fonte
1
Às vezes, você precisa de um código que funcione no Python 2 e 3. Essa é uma boa solução.
precisa
3
O problema é que, com isso, código que usa os dois rangee xrangese comporta de maneira diferente. Não basta fazer isso, também é preciso ter certeza de nunca assumir que rangeestá retornando uma lista (como faria no python 2).
LangeHaare
Você pode usar o xrange deste projeto. Existe uma futurizeferramenta para converter automaticamente o seu código-fonte: python-future.org/…
guettli
17

O rangetipo do Python 3 funciona como o Python 2 xrange. Não sei por que você está vendo uma desaceleração, pois o iterador retornado por sua xrangefunção é exatamente o que você obteria se iterasse rangediretamente.

Não consigo reproduzir a desaceleraçã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

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

Eu aprendi recentemente que o rangetipo do Python 3 tem outros recursos interessantes, como o suporte para fatiar: range(10,100,2)[5:25:5]is range(20, 60, 10)!

Blckknght
fonte
Talvez a desaceleração venha da pesquisa do novo xrangetantas vezes, ou isso é feito apenas uma vez?
askewchan
Um iterador realmente aumenta a velocidade? Eu pensei que apenas economizava memória.
askewchan
3
@ Catalesia Eu acho que o ponto aqui é que nãoxrange foi removido, apenas renomeado .
askewchan
1
@Blckknght: Felicidades, mas ainda é uma merda ter uma explicação do tipo: "Definir literais e compreensões [19] [20] [concluído] {x} significa conjunto ([x]); {x, y} significa conjunto ([ x, y]). {F (x) para x em S se P (x)} significa definido (F (x) para x em S se P (x)). NB. {intervalo (x)} significa definido ( [range (x)]), NOT set (range (x)). Não há literal para um conjunto vazio; use set () (ou {1} & {2} :-). Não há literal frozenset; eles são muito raramente necessário. "
catalesia 22/02
3
A maior vitória no 3.x range, no que me diz respeito, é o tempo constante __contains__. Os novatos costumavam escrever 300000 in xrange(1000000)e isso fazia com que iterasse o todo xrange(ou pelo menos os primeiros 30%), então tivemos que explicar por que essa era uma má ideia, mesmo que pareça tão pitônica. Agora, é pitônico.
abarnert
1

Uma maneira de corrigir seu código python2 é:

import sys

if sys.version_info >= (3, 0):
    def xrange(*args, **kwargs):
        return iter(range(*args, **kwargs))
andrew pate
fonte
1
O ponto está em python3 xrange não está definido, portanto, o código legado que usou as quebras de xrange.
precisa saber é o seguinte
não, basta definir range = xrangecomo está no comentário por @ John La Roy
mimi.vx
@ mimi.vx Não tenho certeza que range = xrange funcionaria no Python3 porque o xrange não está definido. Meu comentário se refere ao caso em que você tem um código legado antigo que contém chamadas xrange E sua tentativa de executá-lo no python3.
andrew pate
1
Ah, meu mau .. xrange = range... i mudado declarações
mimi.vx
range é um iiterador, e de qualquer maneira isso seria uma péssima idéia, mesmo que não fosse, porque ele precisa descompactar o intervalo inteiro primeiro e perde as vantagens de usar um iterador para esse tipo de coisa. Então, a resposta correta não é "range = xrange" seu "xrange = range"
Shayne
0

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

Michel Fernandes
fonte
Não, o alcance não é um interator. Você não pode fazer next () com essa estrutura. Para mais informações, você pode conferir aqui treyhunner.com/2018/02/python-range-is-not-an-iterator
Michel Fernandes
Muito obrigado pelo esclarecimento. Mas vou reafirmar a intenção do comentário original, e é que PY3 range()é o equivalente a PY2 xrange(). E, portanto, no PY3 xrange()é redundante.
Stephen Rauch
-2

comp: ~ $ python Python 2.7.6 (padrão, 22 de junho de 2015, 17:58:13) [GCC 4.8.2] no 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

Com timeit number = 1 param:

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

0.2245171070098877

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

0.10750913619995117

comp: ~ $ python3 Python 3.4.3 (padrão, 14 de outubro de 2015, 20:28:29) [GCC 4.8.4] no 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

Com timeit number = 1,2,3,4, o param funciona de maneira rápida e linear:

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

0.09329321900440846

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

0.18501482300052885

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

0.2703447980020428

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

0.36209142999723554

Parece que, se medirmos 1 ciclo de loop em execução, como timeit.timeit ("[x para x no intervalo (1000000) se x% 4]", número = 1) (como realmente usamos no código real) python3 funciona rápido o suficiente, mas em loops repetidos o python 2 xrange () vence em velocidade contra o range () do python 3.

dmitriy
fonte
mas isso é pela própria linguagem ... nada a ver com xrange / range.
mimi.vx