Почему в Python3 нет функции xrange?

273

Недавно я начал использовать Python3, и у него нет проблем с xrange.

Простой пример:

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

Результаты, соответственно:

1) 1,53888392448 2) 3,215819835662842

Это почему? Я имею в виду, почему Xrange был удален? Это такой отличный инструмент для изучения. Для начинающих, как и я, как и все мы были в какой-то момент. Зачем удалять это? Может кто-нибудь указать мне на правильный ПКП, я не могу его найти.

Приветствия.

catalesia
источник
231
rangeв Python 3.x взят xrangeиз Python 2.x. Это был фактически Python 2.x, rangeкоторый был удален.
Аноров
27
PS, вы никогда не должны время с time. Помимо того, что его проще использовать и труднее ошибиться, а также повторять для вас тесты, он timeitзаботится обо всех вещах, о которых вы не помните или даже не знаете, как позаботиться (например, об отключении GC), и может использовать часы с разрешением в тысячи раз лучше.
abarnert
7
Кроме того , почему вы тестирование времени для фильтрации rangeна x%4 == 0? Почему бы не просто проверить list(xrange())против list(range()), так что , как мало посторонней работы , как это возможно? (Например, как вы узнаете, что 3.x не работает x%4медленнее?) В этом отношении, почему вы строите огромный list, который включает в себя большое выделение памяти (который, помимо того, что он медленный, также невероятно изменчив)? ?
abarnert
5
См. Docs.python.org/3.0/whatsnew/3.0.html , раздел «Представления и итераторы вместо списков»: «range () теперь ведет себя так же, как и xrange (), за исключением случаев, когда он работает со значениями произвольного размера. больше не существует." Итак, range теперь возвращает итератор. iter(range)избыточно
ToolmakerSteve
9
Извините, но я понял, что цитирование документа об изменениях не делает его очевидным. Для тех, кто смущен и не хочет читать давно принятый ответ и все его комментарии: везде, где вы использовали xrange в python 2, используйте range в python 3. Он делает то, что раньше делал xrange, что вернуть итератор. Если вам нужны результаты в списке, сделайте list(range(..)). Это эквивалентно диапазону Python 2. Или сказать по-другому: xrange был переименован в range, потому что он лучше по умолчанию; не было необходимости иметь оба, делайте, list(range)если вам действительно нужен список. ,
ToolmakerSteve

Ответы:

175

Некоторые измерения производительности, используя timeitвместо того, чтобы пытаться сделать это вручную time.

Во-первых, Apple 2.7.2 64-битная:

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

Теперь python.org 3.3.0 64-bit:

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

По- видимому, 3.x rangeдействительно немного медленнее , чем 2.x xrange. И xrangeфункция ОП не имеет к этому никакого отношения. (Не удивительно, поскольку одноразовый вызов в __iter__слот вряд ли будет виден среди 10000000 вызовов, что бы ни происходило в цикле, но кто-то поднял это как возможность.)

Но это только на 30% медленнее. Как ОП получил 2x как медленный? Что ж, если я повторю те же тесты с 32-битным Python, я получу 1,58 против 3,12. Поэтому я предполагаю, что это еще один из тех случаев, когда 3.x был оптимизирован для 64-битной производительности таким образом, что это наносит ущерб 32-битной системе.

Но действительно ли это имеет значение? Проверьте это, с 3.3.0 64-битными снова:

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

Таким образом, сборка listзанимает более чем вдвое больше времени, чем вся итерация.

А что касается «потребляет гораздо больше ресурсов, чем Python 2.6+», то из моих тестов похоже, что 3.x rangeточно такого же размера, что и 2.x xrange- и, даже если он в 10 раз больше, создает ненужный список все еще на 10000000 раз больше проблем, чем любая другая итерация диапазона.

А как насчет явного forцикла вместо цикла C внутри 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

Таким образом, на forутверждение тратится почти столько же времени, сколько и на реальную работу по итерации range.

Если вы беспокоитесь об оптимизации итерации объекта диапазона, вы, вероятно, смотрите не в том месте.


Между тем вы продолжаете спрашивать, почему xrangeбыл удален, независимо от того, сколько раз люди говорили вам одно и то же, но я повторю это еще раз: он не был удален: он был переименован range, а 2.x range- это то, что было удалено.

Вот некоторые доказательства того, что rangeобъект 3.3 является прямым потомком xrangeобъекта 2.x (а не rangeфункции 2.x ): источник 3.3range и 2.7xrange . Вы даже можете увидеть историю изменений (я думаю, что она связана с изменением, которое заменило последний экземпляр строки «xrange» в любом месте файла).

Итак, почему это медленнее?

Ну, например, они добавили много новых функций. С другой стороны, они сделали все виды изменений повсеместно (особенно внутри итерации), которые имеют незначительные побочные эффекты. И было проделано много работы, чтобы значительно оптимизировать различные важные случаи, даже если иногда они слегка пессимизируют менее важные случаи. Добавьте все это, и я не удивлен, что итерация rangeкак можно быстрее теперь немного медленнее. Это один из тех менее важных случаев, на которых никто никогда не будет обращать внимания. Ни у кого, вероятно, никогда не будет реального сценария использования, где эта разница в производительности является горячей точкой в ​​их коде.

abarnert
источник
Но это только на 30% медленнее. Все еще медленнее, но отличный ответ, приятель, о чем подумать. Это не отвечает на мой вопрос, хотя: почему был удален Xrange ?? Подумайте об этом следующим образом: если бы у вас было приложение, зависящее от производительности, основанное на многопроцессорности, знающее, сколько очереди вам нужно потреблять за раз, изменится 30% или нет? Видите ли, вы говорите, что это не имеет значения, но каждый раз, когда я использую диапазон, я слышу, как громкий огорчающий звук вентилятора означает, что процессор хуже, а xrange этого не делает. Подумайте об этом;)
catalesia
9
@catalesia: Еще раз, он не был удален, он был просто переименован range. rangeОбъект в 3.3 является прямым потомком xrangeобъекта в 2.7, а не из rangeфункции в 2.7. Это все равно что спросить, пока itertools.imapубрали в пользу map. Ответа нет, потому что такого не было.
abarnert
1
@catalesia: Небольшие изменения в производительности, по-видимому, не являются результатом прямого решения о дизайне, чтобы сделать диапазоны медленнее, но побочным эффектом 4-летних изменений во всем Python, которые сделали многие вещи быстрее, некоторые вещи немного медленнее (и некоторые вещи быстрее на x86_64, но медленнее на x86, или быстрее в некоторых случаях, но медленнее в других и т. д.). Никто, вероятно, не беспокоился о 30% -ной разнице в том, сколько времени потребуется для итерации range, не делая ничего другого.
abarnert
1
«Никто, вероятно, не беспокоился о 30% -ной разнице в том, сколько времени потребуется для итерации диапазона , не делая ничего другого». Точно.
Каталезия
18
@catalesia: Да, именно так. Но вы, кажется, думаете, что это означает противоположное тому, что говорится. Это не тот случай использования, о котором кто-либо когда-либо будет заботиться, поэтому никто не заметил, что он стал на 30% медленнее. Ну и что? Если вам удастся найти реальную программу, которая в Python 3.3 будет работать медленнее, чем в 2.7 (или 2.6), люди будут об этом заботиться. Если вы не можете, они не будут, и вы не должны.
abarnert
141

Диапазон Python3 в это xrange python2 в. Там нет необходимости обернуть его вокруг него. Чтобы получить актуальный список в Python3, вам нужно использоватьlist(range(...))

Если вы хотите что-то, что работает с Python2 и Python3, попробуйте это

try:
    xrange
except NameError:
    xrange = range
Джон Ла Рой
источник
1
Иногда вам нужен код, который работает на Python 2 и 3. Это хорошее решение.
Грег Глокнер,
3
Проблема в том, что с этим код, который использует оба rangeи xrangeбудет вести себя по-разному. Этого недостаточно, нужно также убедиться, что вы никогда не предполагаете, что rangeэто возвращает список (как это было бы в python 2).
LangeHaare
Вы можете использовать xrange из этого проекта. Существует futurizeинструмент для автоматического преобразования исходного кода: python-future.org/…
guettli
17

rangeТип Python 3 работает так же, как Python 2 xrange. Я не уверен, почему вы видите замедление, поскольку итератор, возвращаемый вашей xrangeфункцией, - это именно то, что вы получите, если бы итерировали rangeнапрямую.

Я не могу воспроизвести замедление в моей системе. Вот как я тестировал:

Python 2, с 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 с rangeнемного быстрее:

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

Недавно я узнал, что rangeтип Python 3 имеет некоторые другие полезные функции, такие как поддержка нарезки: range(10,100,2)[5:25:5]есть range(20, 60, 10)!

Blckknght
источник
Возможно, замедление происходит из-за поиска нового xrangeстолько раз, или это происходит только один раз?
Askewchan
Действительно ли итератор увеличивает скорость в любом случае? Я думал, что это просто спасло память.
Askewchan
3
@catalesia Я думаю, что дело в том, что xrangeэто не было удалено, просто переименовано .
Askewchan
1
@Blckknght: ура, но все равно отстойно, имея объяснение, подобное: «Установить литералы и понимания [19] [20] [сделано] {x} означает множество ([x]); {x, y} означает множество ([ x, y]). {F (x) для x в S, если P (x)} означает набор (F (x) для x в S, если P (x)). NB. {range (x)} означает set ( [range (x)]), NOT set (range (x)). Там нет литерала для пустого набора; используйте set () (или {1} & {2} :-). Там нет литерала frozenset; они слишком редко требуется. "
catalesia
3
rangeНасколько мне известно, самая большая победа в 3.x - это постоянное время __contains__. Новички обычно писали, 300000 in xrange(1000000)и это заставляло его перебирать целое xrange(или, по крайней мере, первые 30%), поэтому нам пришлось объяснить, почему это была плохая идея, даже если она выглядит такой питонической. Теперь, это вещий.
abarnert
1

Один из способов исправить код на python2:

import sys

if sys.version_info >= (3, 0):
    def xrange(*args, **kwargs):
        return iter(range(*args, **kwargs))
Эндрю Пэйт
источник
1
Дело в том, что в Python3 xrange не определен, поэтому устаревший код, который использовал разрывы xrange.
андрей паштет
нет, просто определите range = xrangeкак есть в комментарии @John La Roy
mimi.vx
@ mimi.vx Не уверен, что range = xrange будет работать в Python3, потому что xrange не определен. Мой комментарий относится к случаю, когда у вас есть старый унаследованный код, который содержит вызовы xrange И вы пытаетесь заставить его работать под python3.
Эндрю Пэйт
1
Ах, мой плохой ... xrange = range... я сменил заявления
mimi.vx
Диапазон IS iiterator, и в любом случае это будет ужасной идея , даже если это не так, потому что он должен распаковать весь диапазон первым и теряет преимущество использования итератора для такого рода вещи. Таким образом, правильный ответ - это не «range = xrange», а «xrange = range»
Шейн
0

xrange из Python 2 является генератором и реализует итератор, а range - просто функцией. В Python3 я не знаю, почему выпал Xrange.

Мишель Фернандес
источник
Нет, диапазон не является интегратором. Вы не можете делать следующее () с этой структурой. Для получения дополнительной информации, вы можете проверить здесь treyhunner.com/2018/02/python-range-is-not-an-iterator
Мишель Фернандес
Большое спасибо за разъяснения. Но я повторю намерение исходного комментария, и это то, что PY3 range()является эквивалентом PY2 xrange(). И, таким образом, в PY3 xrange()является избыточным.
Стивен Раух
-2

comp: ~ $ python Python 2.7.6 (по умолчанию, 22 июня 2015 г., 17:58:13) [GCC 4.8.2] в 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

С номером time = 1 параметр:

>>> 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 (по умолчанию, 14 октября 2015 г., 20:28:29) [GCC 4.8.4] в 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

С timeit number = 1,2,3,4 param работает быстро и линейно:

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

Поэтому кажется, что если мы измерим 1 цикл работающего цикла, например timeit.timeit («[x для x в диапазоне (1000000), если x% 4]», число = 1) (как мы фактически используем в реальном коде), python3 работает достаточно быстро, но в повторяющихся циклах python 2 xrange () выигрывает в скорости против range () у python 3.

Дмитрий
источник
но это связано с самим языком ... ничего общего с xrange / range.
mimi.vx