Вы всегда должны отдавать предпочтение xrange (), а не range ()?

460

Почему или почему нет?

mbac32768
источник
36
Может кто-нибудь кратко описать разницу между двумя для нас, не питонов, ребят? Может быть, что-то вроде «xrange () выполняет все функции range (), но также поддерживает X, Y и Z»
Outlaw Programmer
87
range (n) создает список, содержащий все целые числа 0..n-1. Это проблема, если вы делаете диапазон (1000000), потому что в итоге вы получите список> 4Mb. xrange справляется с этим, возвращая объект, который претендует на то, чтобы быть списком, но просто вычисляет необходимое число из запрошенного индекса и возвращает его.
Брайан
4
См. Stackoverflow.com/questions/94935
Джеймс МакМахон,
4
По сути, тогда range(1000)как a list, xrange(1000)это объект, который действует как a generator(хотя это, безусловно, не один). Кроме того, xrangeбыстрее. Вы можете import timeit from timeitи затем создать метод, который просто имеет for i in xrange: passи другой для range, затем сделать timeit(method1)и timeit(method2), и, о чудо, xrange иногда почти в два раза быстрее (то есть, когда вам не нужен список). (Для меня, для i in xrange(1000):passvs for i in range(1000):passпотребовалось 13.316725969314575vs 21.190124988555908секунд соответственно - это много.)
dylnmc
Еще один тест производительности дает xrange(100)на 20% быстрее, чем range(100).
Евгений Сергеев

Ответы:

443

Для производительности, особенно когда вы выполняете итерации в большом диапазоне, xrange()обычно лучше. Тем не менее, есть еще несколько случаев, по которым вы можете предпочесть range():

  • В Python 3 range()делает то , что xrange()раньше делал и xrange()не существует. Если вы хотите написать код, который будет работать как на Python 2, так и на Python 3, вы не сможете его использовать xrange().

  • range()на самом деле может быть быстрее в некоторых случаях - например. если итерация по одной и той же последовательности несколько раз. xrange()должен восстанавливать целочисленный объект каждый раз, но range()будет иметь реальные целочисленные объекты. (Это всегда будет работать хуже с точки зрения памяти, однако)

  • xrange()не используется во всех случаях, когда нужен реальный список. Например, он не поддерживает слайсы или любые методы списка.

[Редактировать] Есть пара постов, в которых рассказывается, как range()будет обновляться с помощью инструмента 2to3. Для справки, вот результат запуска инструмента на некоторых примерах использования range()иxrange()

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)

Как вы можете видеть, при использовании в цикле for или в понимании, или там, где уже заключен в list (), диапазон остается неизменным.

Брайан
источник
5
Что вы подразумеваете под "диапазон станет итератором"? Разве это не должно быть "генератором"?
Майкл Миор
4
Нет. Генератор относится к конкретному типу итератора, а новый rangeв любом случае не является итератором.
user2357112 поддерживает Monica
Твоя вторая пуля не имеет никакого смысла. Вы говорите, что не можете использовать объект несколько раз; что вы можете! попробуйте xr = xrange(1,11)потом на следующей строке for i in xr: print " ".join(format(i*j,"3d") for j in xr)и вуаля! У вас есть расписание до десяти. Он работает так же, как r = range(1,11)и for i in r: print " ".join(format(i*j,"3d") for j in r)... все это объект в Python2. Я думаю, что вы хотели сказать, так это то, что вы можете лучше использовать индексное понимание (если это имеет смысл) rangeв отличие от xrange. Я думаю, что диапазон удобен очень редко
dylnmc
(Не хватает места) Хотя, я делать думаю , что rangeможет быть удобно , если вы хотите использовать listв цикле , а затем изменить некоторые показатели , основанные на определенных условиях или дописать вещей в этом списке, то range, безусловно , лучше. Тем не менее, xrangeон просто быстрее и использует меньше памяти, поэтому для большинства циклических приложений он кажется лучшим. Есть случаи - возвращаясь к вопросу спрашивающего - редко, но существуют, где rangeбыло бы лучше. Возможно, не так редко, как я думаю, но я, безусловно, использую xrange95% времени.
2011 года
129

Нет, у них обоих есть свои применения:

Используйте xrange()при итерации, так как это экономит память. Сказать:

for x in xrange(1, one_zillion):

скорее, чем:

for x in range(1, one_zillion):

С другой стороны, используйте, range()если вы действительно хотите список чисел.

multiples_of_seven = range(7,100,7)
print "Multiples of seven < 100: ", multiples_of_seven
Дэн Ленски
источник
42

Вы должны способствовать range()более xrange()только тогда , когда вам нужен фактический список. Например, когда вы хотите изменить список, возвращенный range()или когда вы хотите нарезать его. Для итерации или даже просто нормального индексирования xrange()будет работать нормально (и, как правило, гораздо эффективнее). Есть момент, когда range()он немного быстрее, чем xrange()для очень маленьких списков, но в зависимости от вашего оборудования и различных других деталей, безубыточность может быть результатом длины 1 или 2; не о чем беспокоиться. Предпочитают xrange().

Томас Воутерс
источник
30

Еще одно отличие состоит в том, что xrange () не может поддерживать числа больше C, поэтому, если вы хотите иметь диапазон с использованием встроенной поддержки больших чисел в Python, вы должны использовать 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 не имеет этой проблемы:

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)
Брайан Минтон
источник
13

xrange()более эффективен, потому что вместо генерации списка объектов, он просто генерирует один объект за раз. Вместо 100 целых чисел, всех их накладных расходов и списка, в который они помещаются, у вас есть только одно целое число за раз. Более быстрое поколение, лучшее использование памяти, более эффективный код.

Если мне не нужен какой-то список, я всегда одобряю xrange()

Дэн Удей
источник
8

range () возвращает список, xrange () возвращает объект xrange.

xrange () немного быстрее и эффективнее с точки зрения памяти. Но выигрыш не очень большой.

Дополнительная память, используемая списком, конечно, не просто тратится впустую, списки обладают большей функциональностью (слайс, повтор, вставка, ...). Точные различия можно найти в документации . Там нет правила костей, используйте то, что нужно.

Python 3.0 все еще находится в разработке, но IIRC range () будет очень похож на xrange () из 2.X, а list (range ()) может использоваться для генерации списков.

jschultz
источник
5

Я просто хотел бы сказать, что ДЕЙСТВИТЕЛЬНО не так сложно получить объект xrange с функциями слайса и индексации. Я написал некоторый код, который работает довольно хорошо и так же быстро, как xrange, когда он считается (итерации).

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)

Честно говоря, я думаю, что вся эта проблема довольно глупа, и Xrange все равно должен делать все это ...

Гаррет Берг
источник
Да, согласился; из совершенно другой технологии, проверьте работу над lodash, чтобы сделать ее ленивой: github.com/lodash/lodash/issues/274 . Нарезка и т. Д. Все еще должна быть как можно более ленивой, а где - нет, только тогда тушить.
Роб Грант
4

Хороший пример, приведенный в книге: Практический Питон Магнус Ли Хетланд

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

Я не рекомендовал бы использовать диапазон вместо xrange в предыдущем примере - хотя необходимы только первые пять чисел, range вычисляет все числа, и это может занять много времени. С xrange это не проблема, потому что он вычисляет только те числа, которые нужны.

Да, я прочитал ответ @ Brian: в python 3 range () в любом случае является генератором, а xrange () не существует.

Грижеш Чаухан
источник
3

Выберите диапазон по этим причинам:

1) xrange будет уходить в новых версиях Python. Это дает вам легкую совместимость в будущем.

2) диапазон возьмет на себя эффективность, связанную с xrange.

ториальную
источник
13
Не делай этого. xrange () уйдет, но и многое другое. Инструмент, который вы будете использовать для перевода вашего кода Python 2.x в код Python 3.x, автоматически переведет xrange () в range (), но range () будет переведен в менее эффективный список (range ()).
Томас Воутерс
10
Томас: На самом деле это немного умнее, чем это. Он переведет range () в ситуациях, когда ему явно не нужен реальный список (например, в цикле for или в понимании), просто в range (). Только те случаи, когда она присваивается переменной или используется напрямую, должна быть заключена в list ()
Brian
2

Ладно, у всех здесь другое мнение относительно компромиссов и преимуществ xrange по сравнению с диапазоном. Они в основном правильные, xrange - итератор, а диапазон расширяется и создает реальный список. В большинстве случаев вы не заметите разницу между ними. (Вы можете использовать карту с диапазоном, но не с xrange, но он использует больше памяти.)

Однако я думаю, что вы хотите услышать, что предпочтительным вариантом является xrange. Поскольку range в Python 3 является итератором, инструмент преобразования кода 2to3 правильно преобразует все варианты использования xrange в range и выдает ошибку или предупреждение при использовании range. Если вы хотите в будущем легко конвертировать код, вы будете использовать только xrange и list (xrange), когда уверены, что хотите получить список. Я узнал об этом во время спринта CPython на PyCon в этом году (2008) в Чикаго.

Дуглас Мейл
источник
8
Это не правда. Код типа «для x в диапазоне (20)» будет оставлен как диапазон, а код типа «x = range (20)» будет преобразован в «x = list (range (20))» - без ошибок. Кроме того, если вы хотите написать код, который будет работать как в версии 2.6, так и в версии 3.0, range () - ваш единственный вариант без добавления функций совместимости.
Брайан
2
  • range(): range(1, 10) возвращает список от 1 до 10 номеров и сохраняет весь список в памяти.
  • xrange(): Нравится range(), но вместо возврата списка возвращает объект, который генерирует числа в диапазоне по требованию. Для циклов это немного быстрее range()и эффективнее с точки зрения памяти. xrange()Объект похож на итератор и генерирует числа по требованию (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() делает то же самое, что и xrange() и в Python 3, и в Python 3 термин не xrange()существует. В некоторых случаях это range()может быть быстрее, если вы повторяете одну и ту же последовательность несколько раз. xrange()должен восстанавливать целочисленный объект каждый раз, но range()будет иметь реальные целочисленные объекты.

Tushar.PUCSD
источник
2

Хотя xrangeэто быстрее, чем rangeв большинстве случаев, разница в производительности довольно минимальна. Маленькая программа ниже сравнивает итерации по a rangeи 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)

Результаты, приведенные ниже, показывают, что xrangeэто действительно быстрее, но недостаточно, чтобы потеть.

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

Так что во что бы то ни стало используйте xrange, но если вы не на ограниченном оборудовании, не беспокойтесь об этом.

speedplane
источник
Ваш list_lenне используется, и поэтому вы запускаете этот код только для списков длиной 100.
Марк
Я рекомендовал бы на самом деле изменения длины списка:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Mark
Вау, это складывается, чтобы быть хорошей неделей, спасибо, исправлено. Тем не менее, нет большой разницы.
скоростной самолет