Чи завжди ви віддаєте перевагу xrange () над діапазоном ()?


460

Чому чи чому б ні?


36
Чи може хтось коротко описати різницю між двома для нас, непітонськими хлопцями? Можливо, щось на кшталт "xrange () робить кожний діапазон (), але також підтримує X, Y і Z"
Програматор поза законом,

87
range (n) створює список, що містить усі цілі числа 0..n-1. Це проблема, якщо ви зробите діапазон (1000000), оскільки ви отримаєте список> 4 Мб. xrange займається цим, повертаючи об'єкт, який видає себе за список, але просто розбирає необхідне число з потрібного індексу та повертає його.
Брайан


4
В основному, тоді як range(1000)є a list, xrange(1000)це об'єкт, який діє як generator(хоча це, звичайно, не один). Також xrangeшвидше. Ви можете, import timeit from timeitа потім зробити метод, який просто є, for i in xrange: passі інший для range, а потім зробити timeit(method1)і timeit(method2), і ось, ось xrange іноді майже вдвічі швидший (тобто коли вам не потрібен список). (Для мене, відповідно, для i in xrange(1000):passvs i in range(1000):passзайняли 13.316725969314575проти 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)

Як ви бачите, при використанні в циклі або для розуміння, або там, де вже обгорнуто список (), діапазон залишається незмінним.


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% часу.
dylnmc

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 ints, тому, якщо ви хочете мати діапазон, використовуючи підтримку великого числа 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 () буде дуже схожий на xrange () 2.X, а list (range ()) може використовуватися для генерації списків.


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/isissue/274 . Нарізання тощо повинно залишатися якомога лінивішим, а де ні, лише тоді повторюйте.
Роб Грант

4

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

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

Я б не рекомендував використовувати діапазон замість xrange у попередньому прикладі - хоча потрібні лише перші п’ять цифр, діапазон обчислює всі числа, і це може зайняти багато часу. З xrange це не проблема, оскільки він обчислює лише ті необхідні числа.

Так, я прочитав відповідь @ Брайана: У python 3, range () все одно є генератором, а xrange () не існує.


3

Перейдіть з діапазоном з цих причин:

1) xrange піде в нових версіях Python. Це дає вам легку сумісність у майбутньому.

2) діапазон займе ефективність, пов'язану з xrange.


13
Не робіть цього. xrange () піде, але так само буде багато інших речей. Інструмент, який ви будете використовувати для перекладу свого коду Python 2.x у код Python 3.x, автоматично переведе xrange () в range (), але range () буде переведено на менш ефективний список (range ()).
Томас Вутерс

10
Томас: Це насправді трохи розумніше за це. Він переведе діапазон () у ситуаціях, коли він очевидно не потребує реального списку (наприклад, у циклі for або розуміння) у просто простір (). Тільки випадки, коли він присвоюється змінній або використовується безпосередньо, повинен бути завернутий списком ()
Brian

2

Гаразд, всі тут різні думки щодо компромісів та переваг xrange у порівнянні з діапазоном. Вони в основному правильні, xrange - це ітератор, а діапазон розгортається та створює фактичний список. У більшості випадків різниці між ними ви дійсно не помітите. (Ви можете використовувати карту з діапазоном, але не з xrange, але вона використовує більше пам'яті.)

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


8
Це не правда. Код типу "для x у діапазоні (20)" буде залишений як діапазон, а код типу "x = діапазон (20)" буде перетворений на "x = список (діапазон (20))" - без помилок. Крім того, якщо ви хочете написати код, який буде працювати під 2.6 і 3.0, діапазон () - це ваш єдиний варіант без додавання функцій порівняльності.
Брайан

2
  • range(): range(1, 10)повертає список від 1 до 10 номерів і зберігає весь список в пам'яті.
  • xrange(): Як range(), але замість повернення списку повертає об'єкт, який генерує числа в діапазоні на вимогу. Для циклічного циклу це легше швидше range()та ефективніше пам'ять. xrange()об'єкта, як ітератор, і генерує числа на вимогу (лінива оцінка).
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, і xrange()в Python 3. не існує терміна, range()насправді може бути швидшим у певному сценарії, якщо ви повторюєте одну і ту ж послідовність кілька разів. xrange()повинен реконструювати цілий об'єкт кожного разу, але range()матиме реальні цілі об'єкти.


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, але якщо ви не маєте обмеженого обладнання, не переживайте над цим.


Ви list_lenне використовуєтесь, тому ви використовуєте цей код лише для списків довжиною 100.
Марк

Я рекомендую фактично змінити довжину списку:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Позначте

Нічого собі, це формується, щоб це був хороший тиждень, спасибі, виправлений. Все-таки не велика різниця.
швидкісний літак
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.