Bạn có nên luôn ưu tiên xrange () trên phạm vi () không?


460

Tại sao hay tại sao không?


36
Ai đó có thể mô tả ngắn gọn về sự khác biệt giữa 2 người đối với chúng tôi không phải là những con trăn không? Có thể một cái gì đó như "xrange () thực hiện mọi phạm vi (), nhưng cũng hỗ trợ X, Y và Z"
Lập trình viên ngoài vòng pháp luật

87
phạm vi (n) tạo danh sách chứa tất cả các số nguyên 0..n-1. Đây là một vấn đề nếu bạn thực hiện phạm vi (1000000), vì bạn sẽ kết thúc với danh sách> 4Mb. xrange xử lý vấn đề này bằng cách trả về một đối tượng giả danh là một danh sách, nhưng chỉ cần tìm ra số cần thiết từ chỉ mục được yêu cầu và trả về đó.
Brian


4
Về cơ bản, trong khi range(1000)là một list, xrange(1000)là một đối tượng hoạt động như một generator(mặc dù nó chắc chắn không phải là một). Ngoài ra, xrangelà nhanh hơn. Bạn có thể import timeit from timeitvà sau đó thực hiện một phương thức vừa có for i in xrange: passvà một phương pháp khác range, sau đó thực hiện timeit(method1)timeit(method2), lo và kìa, xrange đôi khi nhanh gấp đôi (đó là khi bạn không cần một danh sách). (Đối với tôi, lần lượt i in xrange(1000):passso với i in range(1000):passmất 13.316725969314575so với 21.190124988555908giây - đó là rất nhiều.)
dylnmc

Một thử nghiệm hiệu suất khác cho xrange(100)tốc độ nhanh hơn 20% range(100).
Evgeni Sergeev

Câu trả lời:


443

Đối với hiệu suất, đặc biệt là khi bạn lặp trên một phạm vi lớn, xrange()thường tốt hơn. Tuy nhiên, vẫn còn một vài trường hợp tại sao bạn có thể thích range():

  • Trong python 3, range()làm những gì đã xrange()từng làm và xrange()không tồn tại. Nếu bạn muốn viết mã sẽ chạy trên cả Python 2 và Python 3, bạn không thể sử dụng xrange().

  • range()thực sự có thể nhanh hơn trong một số trường hợp - ví dụ. nếu lặp đi lặp lại trên cùng một chuỗi nhiều lần. xrange()phải xây dựng lại đối tượng số nguyên mỗi lần, nhưng range()sẽ có các đối tượng nguyên thực. (Tuy nhiên, nó sẽ luôn hoạt động kém hơn về mặt bộ nhớ)

  • xrange()không thể sử dụng trong mọi trường hợp cần có một danh sách thực sự. Chẳng hạn, nó không hỗ trợ các lát hoặc bất kỳ phương thức liệt kê nào.

[Chỉnh sửa] Có một vài bài viết đề cập đến việc range()công cụ 2to3 sẽ được nâng cấp như thế nào . Đối với bản ghi, đây là đầu ra của việc chạy công cụ trên một số cách sử dụng mẫu của 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)

Như bạn có thể thấy, khi được sử dụng trong một vòng lặp for hoặc hiểu, hoặc nơi được bao bọc bởi list (), phạm vi được giữ nguyên.


5
Bạn có ý nghĩa gì bởi "phạm vi sẽ trở thành một trình vòng lặp"? Đây có nên không phải là "máy phát điện"?
Michael Mior

4
Không. Trình tạo đề cập đến một loại trình vòng lặp cụ thể và rangedù sao mới cũng không phải là trình vòng lặp.
user2357112 hỗ trợ Monica

Viên đạn thứ hai của bạn không thực sự có ý nghĩa gì. Bạn đang nói rằng bạn không thể sử dụng một đối tượng nhiều lần; chắc chắn bạn có thể Hãy thử xr = xrange(1,11)sau đó trên dòng tiếp theo for i in xr: print " ".join(format(i*j,"3d") for j in xr)và thì đấy! Bạn có thời gian của bạn - bảng lên đến mười. Nó hoạt động giống như r = range(1,11)for i in r: print " ".join(format(i*j,"3d") for j in r)... mọi thứ đều là một đối tượng trong Python2. Tôi nghĩ điều bạn muốn nói là bạn có thể hiểu được dựa trên chỉ số (nếu điều đó có ý nghĩa) tốt hơn so với rangetrái ngược với xrange. Range rất tiện dụng, tôi nghĩ rằng
dylnmc

(Không đủ chỗ) Mặc dù, tôi làm cho rằng rangecó thể thuận tiện nếu bạn muốn sử dụng một listtrong một vòng lặp và sau đó thay đổi các chỉ số nhất định dựa trên một số điều kiện hoặc điều append vào danh sách đó, sau đó rangechắc chắn tốt hơn. Tuy nhiên, xrangeđơn giản là nhanh hơn và sử dụng ít bộ nhớ hơn, do đó, đối với phần lớn các ứng dụng vòng lặp, nó dường như là tốt nhất. Có những trường hợp - quay trở lại câu hỏi của người hỏi - hiếm khi nhưng tồn tại, nơi rangenào sẽ tốt hơn. Có lẽ không hiếm khi tôi nghĩ, nhưng tôi chắc chắn sử dụng xrange95% thời gian.
dylnmc

129

Không, cả hai đều có công dụng của chúng:

Sử dụng xrange()khi lặp, vì nó tiết kiệm bộ nhớ. Nói:

for x in xrange(1, one_zillion):

thay vì:

for x in range(1, one_zillion):

Mặt khác, sử dụng range()nếu bạn thực sự muốn một danh sách các số.

multiples_of_seven = range(7,100,7)
print "Multiples of seven < 100: ", multiples_of_seven

42

Bạn nên ủng hộ range()hơn xrange()chỉ khi bạn cần một danh sách thực tế. Chẳng hạn, khi bạn muốn sửa đổi danh sách được trả về range()hoặc khi bạn muốn cắt nó. Đối với phép lặp hoặc thậm chí chỉ là lập chỉ mục bình thường, xrange()sẽ hoạt động tốt (và thường hiệu quả hơn nhiều). Có một điểm range()nhanh hơn một chút so xrange()với các danh sách rất nhỏ, nhưng tùy thuộc vào phần cứng của bạn và các chi tiết khác, điểm hòa vốn có thể là kết quả của độ dài 1 hoặc 2; không có gì phải lo lắng Thích xrange().


30

Một điểm khác biệt nữa là xrange () không thể hỗ trợ các số lớn hơn C ints, vì vậy nếu bạn muốn có một phạm vi sử dụng hỗ trợ số lượng lớn của python, bạn phải sử dụng phạm vi ().

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 không có vấn đề này:

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()hiệu quả hơn vì thay vì tạo danh sách các đối tượng, nó chỉ tạo một đối tượng tại một thời điểm. Thay vì 100 số nguyên và tất cả chi phí hoạt động của chúng và danh sách để đặt chúng vào, bạn chỉ có một số nguyên tại một thời điểm. Tạo nhanh hơn, sử dụng bộ nhớ tốt hơn, mã hiệu quả hơn.

Trừ khi tôi đặc biệt cần một danh sách cho một cái gì đó, tôi luôn ủng hộ xrange()


8

phạm vi () trả về một danh sách, xrange () trả về một đối tượng xrange.

xrange () nhanh hơn một chút và hiệu quả bộ nhớ cao hơn một chút. Nhưng mức tăng không lớn lắm.

Bộ nhớ bổ sung được sử dụng bởi một danh sách tất nhiên không chỉ lãng phí, danh sách có nhiều chức năng hơn (lát, lặp lại, chèn, ...). Sự khác biệt chính xác có thể được tìm thấy trong các tài liệu . Không có quy tắc xương, sử dụng những gì cần thiết.

Python 3.0 vẫn đang được phát triển, nhưng phạm vi IIRC () sẽ rất giống với xrange () của 2.X và danh sách (phạm vi ()) có thể được sử dụng để tạo danh sách.


5

Tôi chỉ muốn nói rằng thật sự không khó để có được một đối tượng xrange có chức năng cắt và lập chỉ mục. Tôi đã viết một số mã hoạt động khá tốt và chỉ nhanh như xrange khi nó đếm (lặp).

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)

Thành thật mà nói, tôi nghĩ rằng toàn bộ vấn đề là loại ngớ ngẩn và xrange nên làm tất cả những điều này ...


Vâng đồng ý; từ một công nghệ hoàn toàn khác, hãy kiểm tra công việc trên lodash để làm cho nó lười biếng: github.com/lodash/lodash/issues/274 . Cắt lát vv vẫn nên lười biếng nhất có thể và ở đâu không, chỉ sau đó thống nhất.
Rob Grant

4

Một ví dụ điển hình được đưa ra trong cuốn sách: Python thực tế của Magnus Lie Hetland

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

Tôi không khuyên bạn nên sử dụng phạm vi thay vì xrange trong ví dụ trước, mặc dù chỉ cần năm số đầu tiên, phạm vi sẽ tính toán tất cả các số và điều đó có thể mất nhiều thời gian. Với xrange, đây không phải là vấn đề vì nó chỉ tính những con số cần thiết.

Có, tôi đã đọc câu trả lời của @ Brian: Trong python 3, Range () dù sao cũng là trình tạo và xrange () không tồn tại.


3

Đi với phạm vi cho những lý do sau:

1) xrange sẽ biến mất trong các phiên bản Python mới hơn. Điều này cung cấp cho bạn khả năng tương thích dễ dàng trong tương lai.

2) phạm vi sẽ đảm nhận hiệu quả liên quan đến xrange.


13
Đừng làm điều này. xrange () sẽ biến mất, nhưng cũng sẽ có rất nhiều thứ khác. Công cụ bạn sẽ sử dụng để dịch mã Python 2.x của mình sang mã Python 3.x sẽ tự động dịch xrange () sang phạm vi (), nhưng phạm vi () sẽ được dịch sang danh sách kém hiệu quả hơn (phạm vi ()).
Thomas Wouters

10
Thomas: Nó thực sự thông minh hơn thế một chút. Nó sẽ dịch phạm vi () trong các tình huống trong đó rõ ràng không cần một danh sách thực (ví dụ: trong vòng lặp for hoặc hiểu) thành phạm vi đơn giản (). Chỉ những trường hợp được gán cho một biến hoặc được sử dụng trực tiếp mới được gói bằng danh sách ()
Brian

2

Được rồi, mọi người ở đây như một ý kiến ​​khác nhau về sự đánh đổi và lợi thế của xrange so với phạm vi. Chúng hầu như chính xác, xrange là một trình vòng lặp và phạm vi làm mờ và tạo ra một danh sách thực tế. Đối với phần lớn các trường hợp, bạn sẽ không thực sự nhận thấy sự khác biệt giữa hai trường hợp. (Bạn có thể sử dụng bản đồ với phạm vi nhưng không phải với xrange, nhưng nó sử dụng nhiều bộ nhớ hơn.)

Tuy nhiên, điều tôi nghĩ rằng bạn muốn nghe, tuy nhiên, đó là lựa chọn ưu tiên là xrange. Do phạm vi trong Python 3 là một trình vòng lặp, công cụ chuyển đổi mã 2to3 sẽ chuyển đổi chính xác tất cả các sử dụng của xrange thành phạm vi và sẽ đưa ra một lỗi hoặc cảnh báo cho việc sử dụng phạm vi. Nếu bạn muốn chắc chắn dễ dàng chuyển đổi mã của mình trong tương lai, bạn sẽ chỉ sử dụng xrange và liệt kê (xrange) khi bạn chắc chắn rằng bạn muốn có một danh sách. Tôi đã học được điều này trong cuộc chạy nước rút CPython tại PyCon năm nay (2008) ở Chicago.


8
Đo không phải sự thật. Mã như "cho x trong phạm vi (20)" sẽ được để lại dưới dạng phạm vi và mã như "x = phạm vi (20)" sẽ được chuyển đổi thành "x = list (phạm vi (20))" - không có lỗi. Hơn nữa, nếu bạn muốn viết mã sẽ chạy trong cả 2.6 và 3.0, phạm vi () là tùy chọn duy nhất của bạn mà không cần thêm các hàm tương thích.
Brian

2
  • range(): range(1, 10) trả về một danh sách từ 1 đến 10 số và giữ toàn bộ danh sách trong bộ nhớ.
  • xrange(): Thích range(), nhưng thay vì trả về một danh sách, trả về một đối tượng tạo ra các số trong phạm vi theo yêu cầu. Đối với vòng lặp, điều này nhanh hơn range()và hiệu quả hơn về bộ nhớ. xrange()đối tượng như một trình vòng lặp và tạo ra các số theo yêu cầu (Đánh giá lười biếng).
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()thực hiện điều tương tự như đã xrange()từng làm trong Python 3 và không xrange()tồn tại thuật ngữ trong Python 3. range()thực sự có thể nhanh hơn trong một số trường hợp nếu bạn lặp lại trên cùng một chuỗi nhiều lần. xrange()phải xây dựng lại đối tượng số nguyên mỗi lần, nhưng range()sẽ có các đối tượng nguyên thực.


2

Mặc dù xrangenhanh hơn rangetrong hầu hết các trường hợp, sự khác biệt về hiệu suất là khá nhỏ. Chương trình nhỏ dưới đây so sánh lặp lại qua a rangexrange:

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)

Kết quả dưới đây cho thấy rằng xrangethực sự nhanh hơn, nhưng không đủ để đổ mồ hôi.

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

Vì vậy, bằng mọi cách sử dụng xrange, nhưng trừ khi bạn sử dụng phần cứng bị hạn chế, đừng quá lo lắng về nó.


Tài khoản của bạn list_lenkhông được sử dụng và do đó bạn chỉ chạy mã này cho các danh sách có độ dài 100.
Đánh dấu

Tôi thực sự khuyên bạn nên sửa đổi độ dài danh sách:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Đánh dấu

Wow, đây là một tuần tốt, cảm ơn, đã sửa. Tuy nhiên, không phải là một sự khác biệt lớn.
bay siêu tốc
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.