Dlaczego w Python3 nie ma funkcji Xrange?

273

Ostatnio zacząłem używać Python3 i brak jest xrange boli.

Prosty przykład:

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

Wyniki to odpowiednio:

1) 1,53888392448 2) 3,215819835662842

Dlaczego? To znaczy, dlaczego xrange został usunięty? To takie świetne narzędzie do nauki. Dla początkujących, tak jak ja, jakbyśmy wszyscy byli w pewnym momencie. Po co to usuwać? Czy ktoś może wskazać mi właściwy PEP, nie mogę go znaleźć.

Twoje zdrowie.

catalesia
źródło
231
rangew Python 3.x pochodzi xrangez Python 2.x. W rzeczywistości rangeusunięto Python 2.x.
Anorow
27
PS, nigdy nie powinieneś mieć czasu time. Poza tym, że jest łatwiejszy w użyciu i trudniejszy do popełnienia błędu, a powtarzanie testów za Ciebie, timeitzajmuje się wszystkimi rzeczami, których nie pamiętasz, a nawet nie wiesz, jak się nimi zająć (np. Wyłączenie GC), i możesz użyć zegar z tysiąc razy lepszą rozdzielczością.
abarnert
7
Ponadto, dlaczego testowanie czas, aby filtrować rangena x%4 == 0? Dlaczego nie po prostu przetestować list(xrange())kontra list(range()), więc jest jak najmniej zbędnej pracy? (Na przykład, skąd wiesz, że 3.x nie działa x%4wolniej?) W takim razie, dlaczego budujesz ogromną list, która wymaga całej alokacji pamięci (która oprócz tego, że jest powolna, jest również niesamowicie zmienna) ?
abarnert
5
Zobacz docs.python.org/3.0/whatsnew/3.0.html , sekcja „Widoki i iteratory zamiast list”: „range () zachowuje się teraz jak xrange (), z wyjątkiem tego, że działa z wartościami o dowolnym rozmiarze. już nie istnieje." Zatem zakres zwraca teraz iterator. iter(range)jest zbędny.
ToolmakerSteve,
9
Przepraszam, zdałem sobie sprawę, że cytowanie dokumentu zmiany nie czyni go oślepiająco oczywistym. Dla każdego, kto jest zdezorientowany i nie chce czytać od dawna przyjętej odpowiedzi i wszystkich jej komentarzy: Gdziekolwiek używałeś xrange w python 2, użyj range w python 3. Robi to, co xrange kiedyś robił, czyli zwraca iterator. Jeśli potrzebujesz wyników na liście, zrób to list(range(..)). Jest to równoważne zasięgowi Pythona 2. Lub mówiąc inaczej: xrange został przemianowany na range, ponieważ jest to lepszy domyślny; nie trzeba było mieć obu, list(range)jeśli naprawdę potrzebujesz listy. .
ToolmakerSteve,

Odpowiedzi:

175

Niektóre pomiary wydajności, używając timeitzamiast próbować to zrobić ręcznie time.

Po pierwsze, Apple 2.7.2 64-bit:

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

Teraz 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

Najwyraźniej 3.x rangenaprawdę jest nieco wolniejszy niż 2.x xrange. A funkcja PO nie xrangema z tym nic wspólnego. (Nic dziwnego, ponieważ jednorazowe połączenie do __iter__automatu prawdopodobnie nie będzie widoczne wśród 10000000 połączeń z tym, co dzieje się w pętli, ale ktoś przywołał to jako możliwość).

Ale to tylko 30% wolniej. W jaki sposób PO stał się 2x tak wolny? Cóż, jeśli powtórzę te same testy z 32-bitowym Pythonem, otrzymam 1,58 vs. 3,12. Domyślam się, że jest to kolejny przypadek, w którym 3.x został zoptymalizowany pod kątem wydajności 64-bitowej w sposób, który szkodzi 32-bitowej.

Ale czy to naprawdę ma znaczenie? Sprawdź to ponownie w wersji 64.0 64-bitowej:

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

Tak więc budowanie listzajmuje więcej niż dwa razy więcej niż cała iteracja.

A jeśli chodzi o „zużywa znacznie więcej zasobów niż Python 2.6+”, z moich testów wygląda na to, że 3.x rangema dokładnie taki sam rozmiar jak 2.x xrange- i nawet jeśli byłby 10 razy większy , budowanie niepotrzebnej listy wciąż stanowi około 10000000 razy większy problem niż cokolwiek, co mogłaby zrobić iteracja zakresu.

A co z wyraźną forpętlą zamiast pętli C w środku 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

Tak więc prawie tyle samo czasu zmarnowano na foroświadczenie, jak na faktyczną pracę nad iteracją range.

Jeśli martwisz się optymalizacją iteracji obiektu zakresu, prawdopodobnie patrzysz w niewłaściwe miejsce.


W międzyczasie wciąż pytasz, dlaczego xrangezostał usunięty, bez względu na to, ile razy ludzie mówią ci to samo, ale powtórzę to jeszcze raz: nie został usunięty: został przemianowany na range, a 2.x rangejest tym, co zostało usunięte.

Oto kilka dowodów na to, że rangeobiekt 3.3 jest bezpośrednim potomkiem xrangeobiektu 2.x (a nie rangefunkcji 2.x ): źródło do 3.3range i 2.7xrange . Możesz nawet zobaczyć historię zmian (powiązaną, jak sądzę, ze zmianą, która zastąpiła ostatnie wystąpienie ciągu „xrange” w dowolnym miejscu pliku).

Dlaczego więc jest wolniejszy?

Po pierwsze, dodali wiele nowych funkcji. Po drugie, wprowadzili wszelkiego rodzaju zmiany w całym miejscu (szczególnie w iteracji), które mają niewielkie skutki uboczne. Dużo pracy włożono w radykalną optymalizację różnych ważnych przypadków, nawet jeśli czasami nieznacznie pesymalizuje mniej ważne przypadki. Dodaj to wszystko i nie dziwię się, że iterowanie rangetak szybko, jak to możliwe, jest teraz nieco wolniejsze. Jest to jeden z tych mniej ważnych przypadków, na których nikt nigdy by się nie przejmował, na czym mógł się skupić. Nikt nigdy nie będzie miał rzeczywistego przypadku użycia, w którym ta różnica wydajności jest hotspotem w kodzie.

abarnert
źródło
Ale to tylko 30% wolniej. Wciąż wolniejszy, ale świetny przyjaciel, coś do przemyślenia. Nie odpowiada jednak na moje pytanie: dlaczego xrange został usunięty? Pomyśl o tym w ten sposób - gdybyś miał aplikację zależną od wydajności opartą na wieloprocesorowości, wiedząc, ile kolejki potrzebujesz zużyć jednorazowo, czy 30% zrobiłoby różnicę, czy nie? Widzisz, mówisz, że to nie ma znaczenia, ale za każdym razem, gdy używam zasięgu, słyszę ten ogromny niepokojący dźwięk wentylatora, co oznacza, że ​​procesor jest na gorszym poziomie, podczas gdy Xrange tego nie robi. Pomyśl o tym;)
catalesia
9
@catalesia: Po raz kolejny nie został usunięty, po prostu zmieniono jego nazwę range. rangeObiekt w 3.3 bezpośredniego potomka tego xrangeprzedmiotu w 2,7, a nie w rangezależności na 2,7. To tak, jakby pytać, kiedy itertools.imapzostał usunięty na korzyść map. Nie ma odpowiedzi, ponieważ nic takiego się nie wydarzyło.
abarnert
1
@catalesia: Niewielkie zmiany wydajności prawdopodobnie nie są wynikiem bezpośredniej decyzji projektowej o spowolnieniu zakresów, ale efektem ubocznym 4 lat zmian w całym Pythonie, które spowodowały, że wiele rzeczy przyspieszyło, niektóre rzeczy nieco wolniej (i niektóre rzeczy szybciej na x86_64, ale wolniej na x86 lub szybciej w niektórych przypadkach użycia, ale wolniej w innych itp.). Nikt prawdopodobnie nie martwił się o 30% różnicę w tym, ile czasu zajmuje iteracja range, nie robiąc nic więcej.
abarnert
1
„Nikt prawdopodobnie nie martwił się o 30% różnicę w tym, ile czasu zajmuje iteracja zakresu , nie robiąc nic innego. ” Dokładnie.
catalesia
18
@catalesia: Tak, dokładnie. Ale wydaje ci się, że to oznacza przeciwieństwo tego, co mówi. Nie jest to przypadek użycia, na którym nikt się nie przejmuje, więc nikt nie zauważył, że jest o 30% wolniejszy. Więc co? Jeśli z tego powodu znajdziesz program, który działa wolniej w Pythonie 3.3 niż w wersji 2.7 (lub 2.6), ludzie będą się tym przejmować. Jeśli nie możesz, nie zrobią tego i ty też nie powinieneś.
abarnert
141

Zakres Python3 to xrange Python2. Nie ma potrzeby owijania go iterem. Aby uzyskać rzeczywistą listę w Python3, musisz użyćlist(range(...))

Jeśli chcesz czegoś, co działa z Python2 i Python3, spróbuj tego

try:
    xrange
except NameError:
    xrange = range
John La Rooy
źródło
1
Czasami potrzebujesz kodu, który działa zarówno w Pythonie 2, jak i 3. To dobre rozwiązanie.
Greg Glockner
3
Problem polega na tym, że dzięki temu kod, który używa obu rangei xrangezachowuje się inaczej. Nie wystarczy to zrobić, trzeba też upewnić się, że nigdy nie zakłada się, że rangezwraca listę (tak jak w Pythonie 2).
LangeHaare
Możesz użyć xrange z tego projektu. Istnieje futurizenarzędzie do automatycznej konwersji kodu źródłowego: python-future.org/…
guettli
17

rangeTyp Python 3 działa tak samo jak Python 2 xrange. Nie jestem pewien, dlaczego obserwujesz spowolnienie, ponieważ iterator zwrócony przez twoją xrangefunkcję jest dokładnie tym, co byś otrzymał, gdybyś iterował rangebezpośrednio.

Nie jestem w stanie odtworzyć spowolnienia w moim systemie. Oto jak testowałem:

Python 2 z 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, z rangejest trochę szybszy:

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

Niedawno dowiedziałem się, że rangetyp Pythona 3 ma kilka innych fajnych funkcji, takich jak obsługa krojenia: range(10,100,2)[5:25:5]is range(20, 60, 10)!

Blckknght
źródło
Być może spowolnienie wynika z wyszukiwania nowego xrangetak wiele razy, czy może to zrobić tylko raz?
askewchan
Czy iterator faktycznie i tak zwiększa prędkość? Myślałem, że to właśnie uratowało pamięć.
askewchan
3
@catalesia Myślę, że chodzi o to, że niexrange został usunięty, tylko zmieniono jego nazwę .
askewchan
1
@Blckknght: Pozdrawiam, ale wciąż jest do bani wyjaśnienie, takie jak: „Ustaw literały i rozumienia [19] [20] [gotowe] {x} oznacza set ([x]); {x, y} oznacza set ([ x, y]). {F (x) dla xw S, jeśli P (x)} oznacza zbiór (F (x) dla xw S, jeśli P (x)). NB. {zakres (x)} oznacza zbiór ( [zakres (x)]), NIE zestaw (zakres (x)). Nie ma literału dla pustego zestawu; użyj set () (lub {1} i {2} :-). Nie ma literału zamrożonego; są też zbyt rzadko potrzebne.
catalesia
3
Według mnie największą wygraną w 3.x rangejest czas stały __contains__. Początkujący pisali, 300000 in xrange(1000000)co powodowało iterację całości xrange(lub przynajmniej pierwszych 30%), więc musieliśmy wyjaśnić, dlaczego był to zły pomysł, nawet jeśli wygląda tak pytonicznie. Teraz jest pytoniczny.
abarnert
1

Jednym ze sposobów naprawienia kodu python2 jest:

import sys

if sys.version_info >= (3, 0):
    def xrange(*args, **kwargs):
        return iter(range(*args, **kwargs))
Andrzej Pasztet
źródło
1
Punkt znajduje się w python3 xrange nie jest zdefiniowany, więc starszy kod, który używał xrange, dzieli się.
andrew pate
nie, po prostu zdefiniuj, range = xrangejak skomentował @John La Roy
mimi.vx
@ mimi.vx Nie jestem pewien, czy zakres = xrange będzie działał w Pythonie 3, ponieważ xrange nie jest zdefiniowany. Mój komentarz odnosi się do przypadku, w którym masz stary kod, który zawiera wywołania xrange ORAZ twoją próbę uruchomienia go pod Python3.
andrew pate
1
Ach, mój zły .. xrange = range... zamieniłem instrukcje
mimi.vx
zakres JEST IITRATOREM, a zresztą byłby to okropny pomysł, nawet gdyby nie był, ponieważ najpierw musi rozpakować cały zakres i traci zalety korzystania z iteratora do tego typu rzeczy. Tak więc odpowiedź nie jest poprawna „zakres = XRange” jej „XRange = range”
Shayne
0

xrange z Python 2 jest generatorem i implementuje iterator, podczas gdy zasięg jest tylko funkcją. W Python3 nie wiem, dlaczego porzucono Xrange.

Michel Fernandes
źródło
Nie, zasięg nie jest interatorem. Nie możesz zrobić next () z tą strukturą. Aby uzyskać więcej informacji, możesz sprawdzić tutaj treyhunner.com/2018/02/python-range-is-not-an-iterator
Michel Fernandes
Dziękuję bardzo za wyjaśnienie. Ale powtórzę zamiar oryginalnego komentarza, i to jest, że PY3 range()jest odpowiednikiem PY2 xrange(). I tak w PY3 xrange()jest zbędny.
Stephen Rauch,
-2

comp: ~ $ python Python 2.7.6 (domyślnie, 22 czerwca 2015, 17:58:13) [GCC 4.8.2] na 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

Z numerem czasu = 1 parametr:

>>> 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 (domyślnie, 14 października 2015, 20:28:29) [GCC 4.8.4] w systemie 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

Z liczbą timeit = 1,2,3,4 param działa szybko i liniowo:

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

Wydaje się więc, że jeśli zmierzymy 1 działający cykl pętli, taki jak timeit.timeit („[x dla xw zakresie (1000000), jeśli x% 4]”, liczba = 1) (tak jak w rzeczywistości używamy w prawdziwym kodzie) python3 działa wystarczająco szybko, ale w powtarzających się pętlach python 2 xrange () wygrywa z prędkością w stosunku do range () z python 3.

dmitriy
źródło
ale to przez sam język ... nie ma nic wspólnego z xrange / range.
mimi.vx