Você deve sempre favorecer xrange () acima do intervalo ()?

460

Por que ou por que não?

mbac32768
fonte
36
Alguém pode descrever brevemente a diferença entre os 2 para nós, não-python? Talvez algo como "xrange () não Everyhing range () faz, mas também suporta X, Y e Z"
Outlaw Programmer
87
range (n) cria uma lista contendo todos os números inteiros 0..n-1. Este é um problema se você atingir o intervalo (1000000), porque você terminará com uma lista> 4Mb. O xrange lida com isso retornando um objeto que finge ser uma lista, mas apenas calcula o número necessário no índice solicitado e retorna isso.
Brian
4
Veja stackoverflow.com/questions/94935
James McMahon
4
Basicamente, enquanto que range(1000)é a list, xrange(1000)é um objeto que age como um generator(embora certamente não seja um). Além disso, xrangeé mais rápido. Você pode import timeit from timeite, em seguida, fazer um método que só tem for i in xrange: passe outro para range, em seguida, fazer timeit(method1)e timeit(method2)e, vejam só, xrange é quase duas vezes mais rápido às vezes (que é quando você não precisa de uma lista). (Para mim, para i in xrange(1000):passvs para i in range(1000):passlevou 13.316725969314575vs 21.190124988555908segundos, respectivamente - que é um monte.)
dylnmc
Outro teste de desempenho é xrange(100)20% mais rápido que range(100).
Evgeni Sergeev

Respostas:

443

Para o desempenho, especialmente quando você está interagindo em uma grande variedade, xrange()geralmente é melhor. No entanto, ainda existem alguns casos pelos quais você pode preferir range():

  • No python 3, range()faz o que xrange()costumava fazer e xrange()não existe. Se você deseja escrever um código que será executado no Python 2 e no Python 3, não poderá usá-lo xrange().

  • range()pode realmente ser mais rápido em alguns casos - por exemplo. se iterando na mesma sequência várias vezes. xrange()precisa reconstruir o objeto inteiro toda vez, mas range()terá objetos inteiros reais. (No entanto, sempre terá um desempenho pior em termos de memória)

  • xrange()não é utilizável em todos os casos em que é necessária uma lista real. Por exemplo, ele não suporta fatias ou qualquer método de lista.

[Editar] Existem algumas postagens mencionando como range()será atualizado pela ferramenta 2to3. Para o registro, aqui está o resultado da execução da ferramenta em alguns exemplos de usos range()exrange()

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 você pode ver, quando usado em um loop ou compreensão for, ou onde já está envolvido com list (), o intervalo permanece inalterado.

Brian
fonte
5
O que você quer dizer com "intervalo se tornará um iterador"? Isso não deveria ser "gerador"?
Michael Mior
4
Não. O gerador se refere a um tipo específico de iterador, e o novo rangenão é um iterador de qualquer maneira.
User2357112 suporta Monica
Sua segunda bala não faz muito sentido. Você está dizendo que não pode usar um objeto várias vezes; Certamente você pode! tente xr = xrange(1,11)então na próxima linha for i in xr: print " ".join(format(i*j,"3d") for j in xr)e pronto! Você tem seus horários até dez. Funciona da mesma forma que r = range(1,11)e for i in r: print " ".join(format(i*j,"3d") for j in r)... tudo é um objeto no Python2. Eu acho que o que você quis dizer é que você pode fazer melhor a compreensão baseada em índice (se isso faz sentido) melhor do rangeque o contrário xrange. Faixa é útil muito raramente, eu acho
dylnmc
(Quarto não suficiente) Embora, eu faço pensar que rangepode ser útil se você quiser usar um listem um loop e, em seguida, mudar certos índices baseados em certas condições ou coisas anexar a essa lista, então rangeé definitivamente melhor. No entanto, xrangeé simplesmente mais rápido e utiliza menos memória; portanto, para a maioria dos aplicativos de loop for, parece ser o melhor. Há casos - voltando à pergunta do interlocutor - raramente, mas existentes, onde rangeseria melhor. Talvez não tão raramente quanto estou pensando, mas certamente uso xrange95% do tempo.
dylnmc
129

Não, ambos têm seus usos:

Use xrange()ao iterar, pois economiza memória. Dizer:

for x in xrange(1, one_zillion):

ao invés de:

for x in range(1, one_zillion):

Por outro lado, use range()se você realmente deseja uma lista de números.

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

Você deve favorecer range()mais xrange()somente quando você precisa de uma lista real. Por exemplo, quando você deseja modificar a lista retornada por range()ou quando deseja cortá-la. Para iteração ou até apenas indexação normal, xrange()funcionará bem (e geralmente com muito mais eficiência). Há um ponto em que range()é um pouco mais rápido do que xrange()para listas muito pequenas, mas, dependendo do seu hardware e de vários outros detalhes, o ponto de equilíbrio pode ser o resultado do comprimento 1 ou 2; não é algo para se preocupar. Preferir xrange().

Thomas Wouters
fonte
30

Uma outra diferença é que xrange () não pode suportar números maiores que C ints; portanto, se você deseja ter um intervalo usando o suporte de grande número incorporado do python, você deve 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 não tem esse 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
fonte
13

xrange()é mais eficiente porque, em vez de gerar uma lista de objetos, apenas gera um objeto por vez. Em vez de 100 números inteiros, toda a sobrecarga e a lista para inseri-los, você apenas tem um número inteiro por vez. Geração mais rápida, melhor uso da memória, código mais eficiente.

A menos que eu precise especificamente de uma lista para alguma coisa, eu sempre a favor xrange()

Dan Udey
fonte
8

range () retorna uma lista, xrange () retorna um objeto xrange.

xrange () é um pouco mais rápido e um pouco mais eficiente em termos de memória. Mas o ganho não é muito grande.

Obviamente, a memória extra usada por uma lista não é desperdiçada, as listas têm mais funcionalidades (cortar, repetir, inserir, ...). As diferenças exatas podem ser encontradas na documentação . Não há regra rígida, use o que for necessário.

O Python 3.0 ainda está em desenvolvimento, mas o intervalo IIRC () será muito semelhante ao xrange () de 2.X e list (range ()) pode ser usado para gerar listas.

jschultz
fonte
5

Gostaria apenas de dizer que REALMENTE não é tão difícil obter um objeto xrange com funcionalidade de fatia e indexação. Eu escrevi um código que funciona muito bem e é tão rápido quanto o xrange para quando conta (iterações).

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, acho que a questão toda é meio boba e o xrange deve fazer tudo isso de qualquer maneira ...

Garrett Berg
fonte
Sim concordou; de uma tecnologia totalmente diferente, verifique o trabalho no lodash para torná-lo preguiçoso: github.com/lodash/lodash/issues/274 . Fatiar, etc, ainda deve ser o mais preguiçoso possível e, onde não, só então reificar.
Rob Grant
4

Um bom exemplo dado no livro: Python Prático Por Magnus Lie Hetland

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

Eu não recomendaria usar o intervalo em vez de xrange no exemplo anterior - embora apenas os cinco primeiros números sejam necessários, o intervalo calcula todos os números e isso pode levar muito tempo. Com o xrange, isso não é um problema, pois calcula apenas os números necessários.

Sim, eu li a resposta do @ Brian: No python 3, range () é um gerador de qualquer maneira e xrange () não existe.

Grijesh Chauhan
fonte
3

Vá com alcance por estes motivos:

1) O xrange desaparecerá nas versões mais recentes do Python. Isso oferece fácil compatibilidade futura.

2) o alcance assumirá as eficiências associadas ao xrange.

histórico
fonte
13
Não faça isso. O xrange () desaparecerá, mas muitas outras coisas também. A ferramenta que você usará para traduzir seu código Python 2.x para o código Python 3.x converterá automaticamente xrange () para range (), mas range () será traduzido para a lista menos eficiente (range ()).
Thomas Wouters
10
Thomas: Na verdade, é um pouco mais inteligente que isso. Ele converterá range () em situações em que claramente não precisa de uma lista real (por exemplo, em um loop for ou compreensão) em apenas range (). Somente casos em que é atribuído a uma variável, ou usados diretamente devem ser acondicionadas com list ()
Brian
2

Ok, todos aqui têm uma opinião diferente sobre as vantagens e desvantagens de xrange versus range. Eles estão na maioria corretos, o xrange é um iterador, e o intervalo é atualizado e cria uma lista real. Na maioria dos casos, você realmente não notará diferença entre os dois. (Você pode usar o mapa com alcance, mas não com xrange, mas ele consome mais memória.)

O que eu acho que você quer ouvir, no entanto, é que a escolha preferida é o intervalo. Como o range no Python 3 é um iterador, a ferramenta de conversão de código 2to3 converterá corretamente todos os usos do xrange em range e emitirá um erro ou aviso para o uso do range. Se você deseja converter facilmente seu código no futuro, usará apenas xrange e listará (xrange) quando tiver certeza de que deseja uma lista. Aprendi isso durante o CPython sprint na PyCon este ano (2008) em Chicago.

Douglas Mayle
fonte
8
Isso não é verdade. Código como "para x no intervalo (20)" será deixado como intervalo e código como "x = intervalo (20)" será convertido em "x = lista (intervalo (20))" - sem erros. Além disso, se você deseja escrever um código que será executado tanto em 2.6 quanto em 3.0, range () é sua única opção sem adicionar funções de compatibilidade.
Brian
2
  • range(): range(1, 10)retorna uma lista de 1 a 10 números e mantém a lista inteira na memória.
  • xrange(): Como range(), mas em vez de retornar uma lista, retorna um objeto que gera os números no intervalo sob demanda. Para loop, isso é levemente mais rápido range()e mais eficiente em memória. xrange()objeto como um iterador e gera os números sob demanda (Lazy Evaluation).
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()faz o mesmo que xrange()costumava fazer no Python 3 e não xrange()existe um termo no Python 3. range()Na verdade, pode ser mais rápido em alguns cenários se você estiver repetindo a mesma sequência várias vezes. xrange()precisa reconstruir o objeto inteiro toda vez, mas range()terá objetos inteiros reais.

Tushar.PUCSD
fonte
2

Embora xrangeseja mais rápido do que rangena maioria das circunstâncias, a diferença de desempenho é bastante mínima. O pequeno programa abaixo compara a iteração sobre a rangee an 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)

Os resultados abaixo mostram que xrangeé realmente mais rápido, mas não o suficiente para transpirar.

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

Portanto, use todos os meios xrange, mas a menos que você esteja usando um hardware restrito, não se preocupe muito com isso.

speedplane
fonte
O seu list_lennão está sendo usado e assim você só está executando este código para listas de comprimento 100.
Mark
Eu recomendo realmente modificar o comprimento lista:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Mark
Uau, isso está se preparando para ser uma boa semana, obrigado, consertado. Ainda assim, não é uma grande diferença.
speedplane 21/03