Czy zawsze powinieneś faworyzować xrange () nad zasięgiem ()?

460

Dlaczego lub dlaczego nie?

mbac32768
źródło
36
Czy ktoś może krótko opisać różnicę między 2 dla nas, którzy nie są pythonami? Może coś w stylu „xrange () robi wszystko, co robi zakres (), ale obsługuje także X, Y i Z”
programista Outlaw Programmer
87
zakres (n) tworzy listę zawierającą wszystkie liczby całkowite 0..n-1. Jest to problem, jeśli wykonasz zasięg (1000000), ponieważ skończysz z listą> 4 Mb. xrange radzi sobie z tym, zwracając obiekt, który udaje listę, ale po prostu oblicza liczbę potrzebną z żądanego indeksu i zwraca to.
Brian
4
Zobacz stackoverflow.com/questions/94935
James McMahon,
4
Zasadniczo, podczas gdy range(1000)jest list, xrange(1000)jest obiektem, który działa jak generator(choć na pewno nie jest jednym). Jest też xrangeszybszy. Możesz, import timeit from timeita następnie stworzyć metodę, która właśnie ma for i in xrange: passinną metodę, a rangenastępnie wykonaj timeit(method1)i timeit(method2), a oto, xrange jest czasami prawie dwa razy szybszy (wtedy, gdy nie potrzebujesz listy). (Dla mnie, dla i in xrange(1000):passvs dla i in range(1000):passzajęło odpowiednio 13.316725969314575vs 21.190124988555908sekundy - to dużo.)
dylnmc
Kolejny test wydajności daje xrange(100)o 20% szybszy niż range(100).
Jewgienij Siergiejew

Odpowiedzi:

443

Wydajność, szczególnie w przypadku iteracji w dużym zakresie, xrange()jest zwykle lepsza. Jednak nadal istnieje kilka przypadków, dla których możesz preferować range():

  • W Pythonie 3 range()robi to , co xrange()kiedyś i xrange()nie istnieje. Jeśli chcesz napisać kod, który będzie działał zarówno w Pythonie 2, jak i Pythonie 3, nie możesz go użyć xrange().

  • range()w niektórych przypadkach może być szybszy - np. jeśli iteruje się wielokrotnie w tej samej sekwencji. xrange()musi za każdym razem rekonstruować obiekt liczb całkowitych, ale range()będzie miał rzeczywiste obiekty liczb całkowitych. (Zawsze jednak będzie gorzej pod względem pamięci)

  • xrange()nie jest użyteczny we wszystkich przypadkach, w których potrzebna jest prawdziwa lista. Na przykład nie obsługuje plasterków ani żadnych metod list.

[Edytuj] Istnieje kilka postów range()mówiących o tym , jak zostanie zaktualizowane narzędzie 2to3. Dla przypomnienia, oto wyniki działania narzędzia na niektórych przykładowych zastosowaniach range()ixrange()

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)

Jak widać, w przypadku użycia w pętli for lub zrozumieniu, lub gdy jest już zawinięty za pomocą list (), zakres pozostaje niezmieniony.

Brian
źródło
5
Co rozumiesz przez „zakres stanie się iteratorem”? Czy nie powinien to być „generator”?
Michael Mior,
4
Nie. Generator odnosi się do określonego typu iteratora, a nowy i rangetak nie jest iteratorem.
użytkownik2357112 obsługuje Monikę
Twoja druga kula naprawdę nie ma sensu. Mówisz, że nie możesz użyć obiektu wiele razy; oczywiście że możesz! spróbuj xr = xrange(1,11)w następnej linii for i in xr: print " ".join(format(i*j,"3d") for j in xr)i voila! Masz swoje tabele czasu do dziesięciu. Działa tak samo jak r = range(1,11)i for i in r: print " ".join(format(i*j,"3d") for j in r)... wszystko w Python2 jest obiektem. Myślę, że chciałeś powiedzieć, że możesz lepiej rozumieć na podstawie indeksu (jeśli ma to sens) rangew przeciwieństwie do xrange. Zasięg jest przydatny bardzo rzadko, jak sądzę
dylnmc
(Nie wystarczająco dużo miejsca) Chociaż ja zrobić myślę, że rangemoże być przydatny, jeśli chcesz używać listw pętli, a następnie zmienić niektóre indeksy oparte na określonych warunków lub rzeczy dołącza do tej listy, to rangejest zdecydowanie lepiej. Jednak,xrange po prostu szybszy i zużywa mniej pamięci, więc dla większości aplikacji pętlowych wydaje się najlepszy. Są przypadki - wracając do pytania pytającego - rzadko, ale istnieją, gdzie rangebyłoby lepiej. Być może nie tak rzadko, jak myślę, ale z pewnością używam xrange95% czasu.
dylnmc
129

Nie, oba mają swoje zastosowania:

Użyj xrange()podczas iteracji, ponieważ oszczędza pamięć. Mówić:

for x in xrange(1, one_zillion):

zamiast:

for x in range(1, one_zillion):

Z drugiej strony, użyj, range()jeśli naprawdę chcesz listę liczb.

multiples_of_seven = range(7,100,7)
print "Multiples of seven < 100: ", multiples_of_seven
Dan Lenski
źródło
42

Należy sprzyjać range()ciągu xrange()tylko kiedy trzeba rzeczywistą listę. Na przykład, gdy chcesz zmodyfikować listę zwróconą przez range()lub chcesz ją pokroić. W przypadku iteracji lub nawet zwykłego indeksowania xrange()będzie działał dobrze (i zwykle znacznie wydajniej). Jest moment, w którym range()jest nieco szybszy niż w xrange()przypadku bardzo małych list, ale w zależności od sprzętu i różnych innych szczegółów, próg rentowności może być wynikiem długości 1 lub 2; nie ma się czym martwić. Wolą xrange().

Thomas Wouters
źródło
30

Inną różnicą jest to, że xrange () nie może obsługiwać liczb większych niż C ints, więc jeśli chcesz mieć zakres za pomocą wbudowanej obsługi dużej liczby Pythona, musisz użyć 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 nie ma tego problemu:

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)
Brian Minton
źródło
13

xrange()jest bardziej wydajny, ponieważ zamiast generować listę obiektów, generuje tylko jeden obiekt na raz. Zamiast 100 liczb całkowitych i wszystkich ich kosztów ogólnych oraz listy do umieszczenia w nich, masz tylko jedną liczbę całkowitą na raz. Szybsze generowanie, lepsze wykorzystanie pamięci, bardziej wydajny kod.

O ile nie potrzebuję konkretnej listy do czegoś, zawsze faworyzuję xrange()

Dan Udey
źródło
8

range () zwraca listę, xrange () zwraca obiekt xrange.

xrange () jest nieco szybszy i nieco bardziej wydajny pod względem pamięci. Ale zysk nie jest bardzo duży.

Dodatkowa pamięć używana przez listę jest oczywiście nie tylko marnowana, listy mają więcej funkcji (wycinanie, powtarzanie, wstawianie, ...). Dokładne różnice można znaleźć w dokumentacji . Nie ma reguły twardej kości, użyj tego, co jest potrzebne.

Python 3.0 jest wciąż w fazie rozwoju, ale range () IIRC będzie bardzo podobny do xrange () 2.X i list (range ()) można użyć do generowania list.

jschultz
źródło
5

Chciałbym tylko powiedzieć, że NAPRAWDĘ nie jest tak trudno uzyskać obiekt Xrange z funkcją wycinania i indeksowania. Napisałem trochę kodu, który działa całkiem nieźle i jest tak samo szybki jak xrange, gdy się liczy (iteracje).

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)

Szczerze mówiąc, myślę, że cała sprawa jest trochę głupia i xrange powinien to wszystko zrobić ...

Garrett Berg
źródło
Tak, zgodził się; z zupełnie innej technologii, sprawdź pracę na lodash, aby go lenić : github.com/lodash/lodash/issues/274 . Krojenie itp. Powinno nadal być tak leniwe, jak to możliwe, a jeśli nie, to tylko wtedy, gdy to nastąpi.
Rob Grant,
4

Dobry przykład podany w książce: Practical Python Autor: Magnus Lie Hetland

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

W poprzednim przykładzie nie zalecałbym używania zakresu zamiast zakresu x - chociaż potrzebne są tylko pierwsze pięć liczb, zakres oblicza wszystkie liczby, co może zająć dużo czasu. W przypadku Xrange nie stanowi to problemu, ponieważ oblicza tylko te liczby, które są potrzebne.

Tak, przeczytałem odpowiedź Briana: W Pythonie 3, range () jest zresztą generatorem, a xrange () nie istnieje.

Grijesh Chauhan
źródło
3

Wybierz zakres z następujących powodów:

1) xrange zniknie w nowszych wersjach Pythona. Zapewnia to łatwą kompatybilność w przyszłości.

2) zakres nabierze wydajności związanych z xrange.

torial
źródło
13
Nie rób tego xrange () zniknie, ale i wiele innych rzeczy. Narzędzie, którego będziesz używać do tłumaczenia kodu Python 2.x na kod Python 3.x, automatycznie przetłumaczy xrange () na range (), ale range () zostanie przetłumaczony na mniej wydajną listę (range ()).
Thomas Wouters
10
Thomas: W rzeczywistości jest nieco mądrzejszy. Przetłumaczy range () w sytuacjach, w których wyraźnie nie potrzebuje prawdziwej listy (np. W pętli for lub zrozumieniu), do zwykłego range (). Jedyne przypadki, gdzie zostanie przypisana do zmiennej, lub bezpośrednio stosowane muszą być opakowane z listy ()
Brian
2

Okej, wszyscy tutaj mają inne zdanie na temat kompromisów i zalet Xrange w porównaniu z zasięgiem. Są w większości poprawne, xrange jest iteratorem, a zakres rozwija się i tworzy rzeczywistą listę. W większości przypadków tak naprawdę nie zauważysz różnicy między nimi. (Możesz używać mapy z zasięgiem, ale nie z Xrange, ale zajmuje więcej pamięci.)

Myślę jednak, że chcesz usłyszeć, że preferowanym wyborem jest xrange. Ponieważ zakres w Pythonie 3 jest iteratorem, narzędzie do konwersji kodu 2to3 poprawnie skonwertuje wszystkie zastosowania xrange na zakres i wyrzuci błąd lub ostrzeżenie dotyczące użycia zakresu. Jeśli chcesz w przyszłości łatwo przekonwertować kod, użyjesz tylko xrange i list (xrange), gdy masz pewność, że chcesz mieć listę. Nauczyłem się tego podczas sprintu CPython w PyCon w tym roku (2008) w Chicago.

Douglas Mayle
źródło
8
To nieprawda. Kod typu „dla x w zakresie (20)” pozostanie jako zakres, a kod typu „x = zakres (20)” zostanie przekonwertowany na „x = lista (zakres (20))” - bez błędów. Ponadto, jeśli chcesz napisać kod, który będzie działał zarówno w wersjach 2.6, jak i 3.0, range () jest Twoją jedyną opcją bez dodawania funkcji kompatybilności.
Brian
2
  • range(): range(1, 10)zwraca listę od 1 do 10 cyfr i utrzymuje całą listę w pamięci.
  • xrange(): Podobnie range(), ale zamiast zwracać listę, zwraca obiekt, który na żądanie generuje liczby w zakresie. W przypadku zapętlania jest to nieco szybsze range()i bardziej wydajne pod względem pamięci.xrange()obiekt jak iterator i generuje liczby na żądanie (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()robi to samo, co xrange()w Pythonie 3, aw Pythonie 3 nie xrange()istnieje termin. range()W niektórych sytuacjach może być szybszy, jeśli będziesz powtarzał tę samą sekwencję wiele razy. xrange()musi za każdym razem rekonstruować obiekt liczb całkowitych, ale range()będzie miał rzeczywiste obiekty liczb całkowitych.

Tushar.PUCSD
źródło
2

Chociaż xrangejest szybszy niż rangew większości przypadków, różnica w wydajności jest dość minimalna. Mały program poniżej porównuje iterację z a rangei 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)

Poniższe wyniki pokazują, że xrangejest rzeczywiście szybszy, ale niewystarczający, aby się pocić.

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

Więc xrangena pewno używaj , ale jeśli nie masz ograniczonego sprzętu, nie przejmuj się zbytnio.

szybowiec
źródło
Twój list_lennie jest używany, więc uruchamiasz ten kod tylko dla list o długości 100.
Mark
Polecam faktycznie zmodyfikować długość listy:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Mark
Wow, to kształtuje się na dobry tydzień, dzięki, naprawione. Nadal nie jest to duża różnica.
speedplane