Чому в 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 видалено? Це такий чудовий інструмент для навчання. Для початківців, як і я, як і всі ми були в якийсь момент. Навіщо його видаляти? Хтось може вказати мені на належний PEP, я не можу його знайти.

Ура.


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 , розділ "Перегляди та ітератори замість списків": "діапазон () тепер поводиться як xrange (), який раніше поводився, за винятком того, що він працює зі значеннями довільного розміру. Останнє більше не існує ». Отже, діапазон тепер повертає ітератор. iter(range)є зайвим.
ToolmakerSteve

9
Вибачте, усвідомлення цитування документа про зміну не робить це сліпо очевидним. Для всіх, хто плутається і не хоче читати давно прийняту відповідь та всі її коментарі: Де б ви не використовували xrange в python 2, використовуйте range в python 3. Це робить те, що раніше робив xrange, а це повернути ітератор. Якщо вам потрібні результати в списку, зробіть це list(range(..)). Це еквівалентно діапазону python 2. Або сказати іншим способом: xrange було перейменовано на діапазон, оскільки він є кращим за замовчуванням; не потрібно було обох, list(range)якщо це справді потрібен список. .
ToolmakerSteve

Відповіді:


175

Деякі вимірювання продуктивності, використовуючи timeit замість того, щоб намагатися робити це вручну time.

По-перше, 64-розрядний Apple 2.7.2:

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-розрядний:

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.xxrange . І xrangeфункція ОП не має нічого спільного. (Не дивно, що одноразовий дзвінок у __iter__слот, ймовірно, не буде видно серед 10000000 дзвінків у будь-якому випадку, що відбувається в циклі, але хтось вивів це як можливість.)

Але це лише на 30% повільніше. Як ОП вийшла в 2 рази настільки повільно? Ну якщо я повторюю ті ж тести з 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.xxrange і навіть якщо він був 10x більшим, будуючи непотрібний список все ще приблизно 10000000x більше проблеми, ніж все, що може зробити ітерація діапазону.

А як щодо явного 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якнайшвидше зараз трохи повільніше. Це один із тих менш важливих випадків, на який ніхто ніколи не піклується, щоб зосередитися. Ніхто, ймовірно, ніколи не матиме випадків використання в реальному житті, коли ця різниця в продуктивності є точкою доступу у їхньому коді.


Але це лише на 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 - це помаранчевий колір 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, with 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)!


Можливо, уповільнення відбувається через пошук нового xrangeстільки разів, чи це робиться лише один раз?
askewchan

Чи дійсно ітератор збільшує швидкість? Я думав, що це просто зберегло пам’ять.
askewchan

3
@catalesia Я думаю, що тут справа в тому, що неxrange було видалено, а просто перейменовано .
askewchan

1
@Blckknght: Будьте здорові, але це все ще гасить, маючи пояснення, як: "Встановити літерали та розуміння [19] [20] [зроблено] {x} означає set ([x]); {x, y} означає set ([ x, y]). {F (x) для x в S, якщо P (x)} означає безліч (F (x) для x в S, якщо P (x)). NB: {range (x)} означає set ( [діапазон (x)]), НЕ встановлено (діапазон (x)). Для порожнього набору немає літералу; використовуйте set () (або {1} & {2} :-). Немає заморожених літералів; вони теж рідко потрібна ".
каталезія

3
Найбільший виграш у 3.x range, наскільки я переживаю, - це постійний час __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 Не впевнений, що діапазон = xrange буде працювати в Python3, оскільки xrange не визначений. Мій коментар стосується випадку, коли у вас є старий застарілий код, що містить дзвінки xrange, і ви намагаєтеся змусити його працювати під python3.
андрю паште

1
Ах, мій поганий .. xrange = range... я переключив заяви
mimi.vx

range IS іітератор, і в будь-якому випадку це було б жахливою ідеєю, навіть якщо цього не було, тому що він повинен спочатку розпакувати весь діапазон і втратить переваги використання ітератора для подібних речей. Тож правильна відповідь - це не "діапазон = xrange", а "xrange = діапазон"
Shayne

0

xrange від Python 2 є генератором і реалізує ітератор, тоді як діапазон - лише функція. У Python3 я не знаю, чому скинув xrange.


Ні, діапазон не є інтератором. Ви не можете зробити наступний () з цією структурою. Для отримання додаткової інформації ви можете перевірити тут treyhunner.com/2018/02/python-range-is-not-an-iterator
Мішель Фернандес

Дуже дякую за роз’яснення. Але я перепрофілюю наміри оригінального коментаря, і це те, що PY3 range()є еквівалентом PY2 xrange(). І таким чином в PY3 xrange()є зайвим.
Стівен Рауч

-2

комп .: ~ $ 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

З номером timeit = 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

комп .: ~ $ 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 = 1,2,3,4 парам працює швидко і лінійно:

>>> 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 () виграє у швидкості проти діапазону () від python 3.


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