คุณควรจะชอบ 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.