คุณควรจะชอบ xrange () มากกว่าช่วง () หรือไม่?


460

ทำไมหรือทำไมไม่?


36
ใครสามารถอธิบายสั้น ๆ ถึงความแตกต่างระหว่าง 2 สำหรับพวกเราที่ไม่ใช่หลาม? บางทีบางอย่างเช่น "xrange () ทำช่วง everyhing () ทำ แต่ก็รองรับ X, Y, และ Z"
Outlaw Programmer

87
range (n) สร้างรายการที่มีจำนวนเต็มทั้งหมด 0..n-1 ปัญหานี้เป็นปัญหาหากคุณมีช่วง (1000000) เนื่องจากคุณจะจบด้วยรายการ> 4Mb xrange เกี่ยวข้องกับสิ่งนี้โดยส่งคืนวัตถุที่อ้างว่าเป็นรายการ แต่เพียงหาจำนวนที่ต้องการจากดัชนีที่ขอและคืนค่านั้น
Brian


4
โดยทั่วไปในขณะที่range(1000)เป็น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.316725969314575vs 21.190124988555908วินาทีตามลำดับ - มันเยอะมาก)
dylnmc

การทดสอบประสิทธิภาพอีกให้xrange(100)ถึง 20% range(100)เร็วกว่า
Evgeni Sergeev

คำตอบ:


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
คุณหมายถึงอะไรโดย "ช่วงจะกลายเป็นตัววนซ้ำ" สิ่งนี้ไม่ควรเป็น "เครื่องกำเนิด" หรือไม่?
Michael Mior

4
ไม่เครื่องกำเนิดไฟฟ้าหมายถึงตัววนซ้ำชนิดหนึ่งโดยเฉพาะและตัวใหม่rangeไม่ใช่ตัววนซ้ำ
user2357112 รองรับ Monica

กระสุนนัดที่สองของคุณไม่สมเหตุสมผลเลย คุณกำลังบอกว่าคุณไม่สามารถใช้วัตถุได้หลายครั้ง แน่นอนคุณสามารถ! ลองxr = xrange(1,11)ในบรรทัดถัดไปfor i in xr: print " ".join(format(i*j,"3d") for j in xr)แล้ว voila! คุณมีตารางเวลาได้มากถึงสิบครั้ง มันทำงานได้เหมือนกัน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 ได้ดังนั้นหากคุณต้องการมีช่วงโดยใช้ไพ ธ อนที่สร้างขึ้นในการรองรับจำนวนมากคุณต้องใช้ 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/issues/274 การหั่น ฯลฯ ควรจะขี้เกียจที่สุดเท่าที่จะเป็นไปได้และไม่ควรทำซ้ำ
Rob Grant

4

ตัวอย่างที่ดีในหนังสือ: Practical Pythonโดย Magnus Lie Hetland

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

ฉันไม่แนะนำให้ใช้ช่วงแทน xrange ในตัวอย่างก่อนหน้าแม้ว่าจะต้องการเพียงห้าหมายเลขแรกเท่านั้น แต่ช่วงจะคำนวณตัวเลขทั้งหมดและอาจใช้เวลานาน ด้วย xrange นี่ไม่ใช่ปัญหาเพราะคำนวณเฉพาะตัวเลขที่ต้องการ

ใช่ฉันอ่านคำตอบของ @ Brian: ใน python 3 range () เป็นตัวสร้างและ xrange () ไม่มีอยู่จริง


3

ไปกับช่วงด้วยเหตุผลเหล่านี้:

1) xrange จะหายไปใน Python เวอร์ชั่นใหม่ สิ่งนี้ทำให้คุณสามารถใช้งานร่วมกันได้ในอนาคตได้ง่าย

2) ช่วงจะใช้ประสิทธิภาพที่เกี่ยวข้องกับ xrange


13
อย่าทำอย่างนี้ xrange () จะหายไป แต่จะมีอีกหลายอย่าง เครื่องมือที่คุณจะใช้ในการแปลรหัส Python 2.x ของคุณเป็น Python 3.x รหัสจะแปล xrange () เป็น range () โดยอัตโนมัติ แต่ range () จะถูกแปลเป็นรายการที่มีประสิทธิภาพน้อยกว่า (range ())
โทมัสวูสเตอร์

10
โทมัส: จริง ๆ แล้วมันฉลาดกว่านิดหน่อย มันจะแปล range () ในสถานการณ์ที่ไม่จำเป็นต้องมีรายการจริง (เช่นใน for for loop หรือ comprehension) เป็น range ธรรมดา () กรณีเดียวที่จะได้รับการกำหนดให้กับตัวแปรหรือใช้โดยตรงจะต้องห่อด้วยรายการ ()
ไบรอัน

2

โอเคทุกคนที่นี่มีความเห็นแตกต่างกันเกี่ยวกับการแลกเปลี่ยนและข้อดีของ xrange เมื่อเทียบกับช่วง ส่วนใหญ่พวกมันถูกต้องส่วน xrange นั้นเป็นตัววนซ้ำและช่วงดึงออกมาและสร้างรายการจริง สำหรับกรณีส่วนใหญ่คุณจะไม่สังเกตเห็นความแตกต่างระหว่างทั้งสองอย่างแท้จริง (คุณสามารถใช้แผนที่ที่มีช่วง แต่ไม่ใช่กับ xrange แต่ใช้หน่วยความจำมากขึ้น)

สิ่งที่ฉันคิดว่าคุณชุมนุมต้องการที่จะได้ยินคือว่าตัวเลือกที่ต้องการคือ xrange เนื่องจาก range ใน Python 3 เป็นตัววนซ้ำเครื่องมือการแปลงรหัส 2to3 จะแปลงการใช้ xrange ไปเป็น range ทั้งหมดอย่างถูกต้องและจะโยนข้อผิดพลาดหรือคำเตือนสำหรับการใช้ช่วง หากคุณต้องการให้แน่ใจว่าแปลงรหัสของคุณได้อย่างง่ายดายในอนาคตคุณจะใช้ xrange เท่านั้นและ list (xrange) เมื่อคุณแน่ใจว่าต้องการรายการ ฉันเรียนรู้สิ่งนี้ในระหว่างการวิ่ง CPython ที่ PyCon ปีนี้ (2008) ในชิคาโก


8
ที่ไม่เป็นความจริง. รหัสเช่น "สำหรับ x ในช่วง (20)" จะถูกปล่อยให้เป็นช่วงและรหัสเช่น "x = ช่วง (20)" จะถูกแปลงเป็น "x = รายการ (ช่วง (20))" - ไม่มีข้อผิดพลาด นอกจากนี้หากคุณต้องการเขียนรหัสที่จะทำงานภายใต้ทั้ง 2.6 และ 3.0 ช่วง () เป็นตัวเลือกเดียวของคุณโดยไม่ต้องเพิ่มฟังก์ชั่นความเข้ากันได้
Brian

2
  • range(): range(1, 10)ส่งคืนรายการจากหมายเลข 1 ถึง 10 และเก็บรายการทั้งหมดไว้ในหน่วยความจำ
  • xrange(): ไลค์range()แต่แทนที่จะส่งคืนรายการส่งคืนออบเจกต์ที่สร้างตัวเลขในช่วงตามต้องการ สำหรับการวนซ้ำนี้จะเร็วกว่าrange()และมีประสิทธิภาพมากขึ้นในหน่วยความจำ xrange()วัตถุเช่นตัววนซ้ำและสร้างตัวเลขตามต้องการ (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()ทำสิ่งเดียวกับที่xrange()เคยทำใน Python 3 และไม่มีคำศัพท์xrange()ใน Python 3 range()จริง ๆ แล้วจะเร็วขึ้นในบางสถานการณ์ถ้าคุณวนซ้ำในลำดับเดียวกันหลาย ๆ ครั้ง xrange()ต้องสร้างวัตถุจำนวนเต็มใหม่ทุกครั้ง แต่range()จะมีวัตถุจำนวนเต็มจริง


2

ในขณะที่xrangeเร็วกว่าrangeในสถานการณ์ส่วนใหญ่ความแตกต่างของประสิทธิภาพค่อนข้างน้อย โปรแกรมเล็ก ๆ ด้านล่างเปรียบเทียบการทำซ้ำ a rangeและa 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 เท่านั้น
Mark

ฉันอยากจะแนะนำการแก้ไขความยาวรายการ:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Mark

ว้าวนี่กำลังจะเป็นสัปดาห์ที่ดีขอบคุณแก้ไขแล้ว ยังไม่แตกต่างที่สำคัญ
เครื่องบินความเร็ว
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.