Câu hỏi Tại sao “1000000000000000 trong phạm vi (1000000000000001)” quá nhanh trong Python 3?


Đó là sự hiểu biết của tôi rằng range() chức năng thực sự một kiểu đối tượng trong Python 3, tạo nội dung của nó trên bay, tương tự như một máy phát điện.

Đây là trường hợp, tôi đã mong đợi dòng sau để mất một lượng thời gian quá mức, bởi vì để xác định liệu 1 nghìn tỷ trong phạm vi, giá trị một nghìn tỷ sẽ phải được tạo ra:

1000000000000000 in range(1000000000000001)

Hơn nữa: có vẻ như không có vấn đề bao nhiêu zeroes tôi thêm vào, việc tính toán nhiều hơn hoặc ít hơn có cùng một lượng thời gian (về cơ bản tức thời).

Tôi cũng đã thử những thứ như thế này, nhưng tính toán vẫn gần như ngay lập tức:

1000000000000000000000 in range(0,1000000000000000000001,10) # count by tens

Nếu tôi cố gắng thực hiện chức năng phạm vi của riêng tôi, kết quả không phải là tốt đẹp !!

def my_crappy_range(N):
    i = 0
    while i < N:
        yield i
        i += 1
    return

Cái gì thế range() đối tượng làm dưới mui xe mà làm cho nó quá nhanh?


Câu trả lời của Martijn Pieters đã được chọn cho sự hoàn chỉnh của nó, nhưng cũng thấy câu trả lời đầu tiên của abarnert để thảo luận tốt về ý nghĩa của nó range là một chính thức trình tự trong Python 3 và một số thông tin / cảnh báo về sự không thống nhất tiềm năng cho __contains__ tối ưu hóa chức năng trên các triển khai Python. câu trả lời khác của abarnert đi vào một số chi tiết hơn và cung cấp liên kết cho những người quan tâm đến lịch sử đằng sau việc tối ưu hóa trong Python 3 (và thiếu tối ưu hóa xrange trong Python 2). Các câu trả lời bằng cách chọc và bởi wim cung cấp mã nguồn C có liên quan và giải thích cho những người quan tâm.


1368
2018-05-06 15:32


gốc


Đừng thử với xrange() trong Python 2 mặc dù. - Ashwini Chaudhary
Lưu ý rằng đây chỉ là trường hợp nếu mặt hàng chúng tôi kiểm tra là bool hoặc là long loại, với các loại đối tượng khác, nó sẽ phát điên. Hãy thử với: 100000000000000.0 in range(1000000000000001) - Ashwini Chaudhary
Ai nói với bạn rằng range là một máy phát điện? - abarnert
@abarnert Tôi nghĩ rằng chỉnh sửa tôi đã để lại sự nhầm lẫn còn nguyên vẹn. - Rick Teachey
@Superbest xrange() đối tượng không có __contains__ phương pháp, do đó, kiểm tra mục phải lặp qua tất cả các mục. Ngoài ra, có một vài thay đổi khác trong range(), giống như nó hỗ trợ cắt (một lần nữa trả về một range đối tượng) và bây giờ cũng có count và index phương pháp để làm cho nó tương thích với collections.Sequence ABC. - Ashwini Chaudhary


Các câu trả lời:


Python 3 range() đối tượng không tạo ra số ngay lập tức; nó là một đối tượng chuỗi thông minh tạo ra các số theo yêu cầu. Tất cả nó chứa là giá trị bắt đầu, dừng và bước của bạn, sau đó khi bạn lặp qua đối tượng, số nguyên tiếp theo được tính mỗi lần lặp.

Đối tượng cũng thực hiện object.__contains__ cái móctính toán nếu số của bạn là một phần trong phạm vi của nó. Tính toán là một hoạt động thời gian không đổi O (1). Không bao giờ cần phải quét qua tất cả các số nguyên có thể có trong phạm vi.

Từ range() tài liệu đối tượng:

Lợi thế của range nhập thường xuyên list hoặc là tuple là một đối tượng phạm vi sẽ luôn lấy cùng một lượng bộ nhớ nhỏ, bất kể kích thước của phạm vi mà nó đại diện (vì nó chỉ lưu trữ start, stop và step giá trị, tính toán các mục riêng lẻ và phụ đề nếu cần).

Vì vậy, ở mức tối thiểu, range() đối tượng sẽ làm:

class my_range(object):
    def __init__(self, start, stop=None, step=1):
        if stop is None:
            start, stop = 0, start
        self.start, self.stop, self.step = start, stop, step
        if step < 0:
            lo, hi = stop, start
        else:
            lo, hi = start, stop
        self.length = ((hi - lo - 1) // abs(step)) + 1

    def __iter__(self):
        current = self.start
        if self.step < 0:
            while current > self.stop:
                yield current
                current += self.step
        else:
            while current < self.stop:
                yield current
                current += self.step

    def __len__(self):
        return self.length

    def __getitem__(self, i):
        if i < 0:
            i += self.length
        if 0 <= i < self.length:
            return self.start + i * self.step
        raise IndexError('Index out of range: {}'.format(i))

    def __contains__(self, num):
        if self.step < 0:
            if not (self.stop < num <= self.start):
                return False
        else:
            if not (self.start <= num < self.stop):
                return False
        return (num - self.start) % self.step == 0

Điều này vẫn còn thiếu một số điều mà một thực tế range() hỗ trợ (chẳng hạn như .index() hoặc là .count() phương pháp, băm, kiểm tra bình đẳng, hoặc cắt), nhưng nên cung cấp cho bạn một ý tưởng.

Tôi cũng đơn giản hóa __contains__ thực hiện để chỉ tập trung vào các bài kiểm tra số nguyên; nếu bạn đưa ra một range() đối tượng một giá trị không nguyên (bao gồm các lớp con của int), bắt đầu quét chậm để xem liệu có khớp hay không, cũng giống như khi bạn sử dụng thử nghiệm ngăn chặn đối với danh sách tất cả các giá trị được chứa. Điều này đã được thực hiện để tiếp tục hỗ trợ các kiểu số khác mà chỉ xảy ra để hỗ trợ kiểm tra bình đẳng với các số nguyên nhưng không được mong đợi để hỗ trợ số học số nguyên là tốt. Xem bản gốc Vấn đề Python đã triển khai thử nghiệm ngăn chặn.


1357
2018-05-06 15:33



Ok, vậy, một phép tính range(1000000000)[index_number] ví dụ: có thể là: return start + step * index_number? - Rick Teachey
@ RickTeachey: yup. Không cần phải tạo ra bất kỳ số trung gian nào nếu bạn có thể tính toán nó. - Martijn Pieters♦
@Veedrac: Tôi không muốn làm cho câu trở nên mất thời gian hơn và đi vào chi tiết của vòng lặp vs iterables. Tôi sẽ xem tôi có thể trang bị lại một chút không. - Martijn Pieters♦
Thực tế thú vị: bởi vì bạn có triển khai hoạt động __getitem__ và __len__, các __iter__ triển khai thực sự không cần thiết. - Lucretiel
@ stefan.schwetschke: đúng, khi bản vá ban đầu được viết, phạm vi chỉ hỗ trợ bắt đầu và dừng giá trị lên đến sys.maxsize, vì vậy có một giới hạn trên. So với len(range_object), so sánh số / mô đun là gần đủ không đổi không quan trọng cho việc phân tích ở đây. - Martijn Pieters♦


Sự hiểu lầm cơ bản ở đây là trong suy nghĩ rằng range là một máy phát điện. Nó không phải. Trong thực tế, nó không phải là bất kỳ loại iterator.

Bạn có thể nói điều này khá dễ dàng:

>>> a = range(5)
>>> print(list(a))
[0, 1, 2, 3, 4]
>>> print(list(a))
[0, 1, 2, 3, 4]

Nếu nó là một máy phát điện, lặp lại nó một lần sẽ xả nó:

>>> b = my_crappy_range(5)
>>> print(list(b))
[0, 1, 2, 3, 4]
>>> print(list(b))
[]

range thực ra là, là một chuỗi, giống như một danh sách. Bạn thậm chí có thể kiểm tra điều này:

>>> import collections.abc
>>> isinstance(a, collections.abc.Sequence)
True

Điều này có nghĩa là nó phải tuân thủ tất cả các quy tắc là một chuỗi:

>>> a[3]         # indexable
3
>>> len(a)       # sized
5
>>> 3 in a       # membership
True
>>> reversed(a)  # reversible
<range_iterator at 0x101cd2360>
>>> a.index(3)   # implements 'index'
3
>>> a.count(3)   # implements 'count'
1

Sự khác biệt giữa một range và một list Đó là range là một lười biếng hoặc là năng động trình tự; nó không nhớ tất cả các giá trị của nó, nó chỉ nhớ nó start, stopstepvà tạo các giá trị theo yêu cầu __getitem__.

(Là một lưu ý phụ, nếu bạn print(iter(a)), bạn sẽ nhận thấy rằng range sử dụng cùng listiterator nhập như list. Nó hoạt động như thế nào? A listiterator không sử dụng bất cứ điều gì đặc biệt về list ngoại trừ thực tế là nó cung cấp một thực hiện C __getitem__, vì vậy nó hoạt động tốt cho range quá.)


Bây giờ, không có gì nói rằng Sequence.__contains__ phải là thời gian không đổi - thực tế, cho các ví dụ rõ ràng về các chuỗi như listNó không phải. Nhưng không có gì mà nói nó không thể được. Và việc triển khai dễ dàng hơn range.__contains__ để kiểm tra nó một cách toán học ((val - start) % step, nhưng với một số phức tạp thêm để đối phó với các bước tiêu cực) hơn là thực sự tạo ra và kiểm tra tất cả các giá trị, vậy tại sao không nên nó làm điều đó một cách tốt hơn?

Nhưng dường như không có bất cứ điều gì trong ngôn ngữ đảm bảo Điều này sẽ xảy ra. Như Ashwini Chaudhari chỉ ra, nếu bạn cho nó một giá trị không tách rời, thay vì chuyển đổi thành số nguyên và thực hiện phép toán, nó sẽ quay trở lại để lặp lại tất cả các giá trị và so sánh từng giá trị. Và chỉ vì phiên bản CPython 3.2+ và PyPy 3.x xảy ra để chứa tối ưu hóa này, và đó là một ý tưởng hay và dễ thực hiện, không có lý do gì mà IronPython hoặc NewKickAssPython 3.x không thể bỏ qua. (Và trên thực tế, CPython 3.0-3.1 không bao gồm nó.)


Nếu range thực sự là một máy phát điện, như my_crappy_rangethì sẽ không có ý nghĩa để kiểm tra __contains__ theo cách này, hoặc ít nhất là cách nó có ý nghĩa sẽ không rõ ràng. Nếu bạn đã lặp lại 3 giá trị đầu tiên, là 1 vẫn in máy phát điện? Nên thử nghiệm 1 làm cho nó lặp lại và tiêu thụ tất cả các giá trị lên đến 1 (hoặc lên đến giá trị đầu tiên >= 1)?


567
2018-05-06 16:01



Đây là một điều khá quan trọng để có được thẳng. Tôi cho rằng sự khác biệt giữa Python 2 và 3 có thể dẫn đến sự nhầm lẫn của tôi vào thời điểm này. Trong mọi trường hợp, tôi đáng lẽ phải nhận ra kể từ đó range được liệt kê (cùng với list và tuple) như một loại trình tự. - Rick Teachey
@ RickTeachey: Trên thực tế, trong 2,6+ (tôi nghĩ; có thể 2,5+), xrange cũng là một chuỗi. Xem 2.7 tài liệu. Trong thực tế, nó luôn luôn là một gần như chuỗi. - abarnert
@ RickTeachey: Thực ra, tôi đã sai; trong 2.6-2.7 (và 3.0-3.1), tuyên bố là một chuỗi, nhưng nó vẫn chỉ là một chuỗi. Xem câu trả lời khác của tôi. - abarnert
Nó không phải là một iterator, nó là một chuỗi (Iterable về Java, IEnumerable of C #) - một cái gì đó với một .__iter__() phương thức sẽ trả về một trình lặp. Nó trong lượt của nó có thể được sử dụng chỉ một lần. - Smit Johnth
@ThomasAhle: Bởi vì range không kiểm tra các loại khi nó không phải là một số nguyên, vì nó luôn luôn có thể một loại có một __eq__ tương thích với int. Chắc chắn rồi, str rõ ràng sẽ không hoạt động, nhưng họ không muốn làm chậm mọi thứ bằng cách kiểm tra rõ ràng tất cả các loại không thể ở trong đó (và sau cùng, một str lớp con có thể ghi đè __eq__ và được chứa trong range). - ShadowRanger


Sử dụng nguồn, Luke!

Trong CPython, range(...).__contains__ (một trình bao bọc phương thức) cuối cùng sẽ ủy quyền cho một phép tính đơn giản để kiểm tra xem giá trị có thể có trong phạm vi đó hay không. Lý do cho tốc độ ở đây là chúng tôi đang sử dụng lý luận toán học về giới hạn, thay vì lặp lại trực tiếp đối tượng phạm vi. Để giải thích logic được sử dụng:

  1. Kiểm tra xem con số có nằm giữa start và stop
  2. Kiểm tra rằng giá trị sải chân không "đẩy mạnh" số của chúng tôi.

Ví dụ, 994 trong range(4, 1000, 2) bởi vì:

  1. 4 <= 994 < 1000
  2. (994 - 4) % 2 == 0.

Mã C đầy đủ được bao gồm bên dưới, có chi tiết hơn một chút do quản lý bộ nhớ và các chi tiết đếm tham chiếu, nhưng ý tưởng cơ bản là có:

static int
range_contains_long(rangeobject *r, PyObject *ob)
{
    int cmp1, cmp2, cmp3;
    PyObject *tmp1 = NULL;
    PyObject *tmp2 = NULL;
    PyObject *zero = NULL;
    int result = -1;

    zero = PyLong_FromLong(0);
    if (zero == NULL) /* MemoryError in int(0) */
        goto end;

    /* Check if the value can possibly be in the range. */

    cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
    if (cmp1 == -1)
        goto end;
    if (cmp1 == 1) { /* positive steps: start <= ob < stop */
        cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
        cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
    }
    else { /* negative steps: stop < ob <= start */
        cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
        cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
    }

    if (cmp2 == -1 || cmp3 == -1) /* TypeError */
        goto end;
    if (cmp2 == 0 || cmp3 == 0) { /* ob outside of range */
        result = 0;
        goto end;
    }

    /* Check that the stride does not invalidate ob's membership. */
    tmp1 = PyNumber_Subtract(ob, r->start);
    if (tmp1 == NULL)
        goto end;
    tmp2 = PyNumber_Remainder(tmp1, r->step);
    if (tmp2 == NULL)
        goto end;
    /* result = ((int(ob) - start) % step) == 0 */
    result = PyObject_RichCompareBool(tmp2, zero, Py_EQ);
  end:
    Py_XDECREF(tmp1);
    Py_XDECREF(tmp2);
    Py_XDECREF(zero);
    return result;
}

static int
range_contains(rangeobject *r, PyObject *ob)
{
    if (PyLong_CheckExact(ob) || PyBool_Check(ob))
        return range_contains_long(r, ob);

    return (int)_PySequence_IterSearch((PyObject*)r, ob,
                                       PY_ITERSEARCH_CONTAINS);
}

"Thịt" của ý tưởng được đề cập trong dòng:

/* result = ((int(ob) - start) % step) == 0 */ 

Như một lưu ý cuối cùng - nhìn vào range_contains ở cuối đoạn mã. Nếu kiểm tra kiểu chính xác không thành công thì chúng tôi không sử dụng thuật toán thông minh được mô tả, thay vào đó hãy quay trở lại tìm kiếm lặp lại câm của phạm vi bằng cách sử dụng _PySequence_IterSearch! Bạn có thể kiểm tra hành vi này trong trình thông dịch (tôi đang sử dụng v3.5.0 tại đây):

>>> x, r = 1000000000000000, range(1000000000000001)
>>> class MyInt(int):
...     pass
... 
>>> x_ = MyInt(x)
>>> x in r  # calculates immediately :) 
True
>>> x_ in r  # iterates for ages.. :( 
^\Quit (core dumped)

286
2018-05-06 15:41



Câu trả lời chính xác. Đây cũng là ví dụ yêu thích mới của tôi về việc sử dụng nhân đạo goto. - brian_o
@brian_o Sẽ được triển khai tốt hơn với try: finally: và một return thay vì goto... - wizzwizz4


Để thêm vào câu trả lời của Martijn, đây là phần liên quan của nguồn (trong C, vì đối tượng phạm vi được viết bằng mã gốc):

static int
range_contains(rangeobject *r, PyObject *ob)
{
    if (PyLong_CheckExact(ob) || PyBool_Check(ob))
        return range_contains_long(r, ob);

    return (int)_PySequence_IterSearch((PyObject*)r, ob,
                                       PY_ITERSEARCH_CONTAINS);
}

Vì vậy đối với PyLong đối tượng (là int trong Python 3), nó sẽ sử dụng range_contains_long để xác định kết quả. Và chức năng đó chủ yếu kiểm tra xem ob nằm trong phạm vi được chỉ định (mặc dù nó có vẻ phức tạp hơn một chút trong C).

Nếu đó không phải là int đối tượng, nó rơi trở lại lặp lại cho đến khi nó tìm thấy giá trị (hoặc không).

Toàn bộ logic có thể được dịch sang giả Python như sau:

def range_contains (rangeObj, obj):
    if isinstance(obj, int):
        return range_contains_long(rangeObj, obj)

    # default logic by iterating
    return any(obj == x for x in rangeObj)

def range_contains_long (r, num):
    if r.step > 0:
        # positive step: r.start <= num < r.stop
        cmp2 = r.start <= num
        cmp3 = num < r.stop
    else:
        # negative step: r.start >= num > r.stop
        cmp2 = num <= r.start
        cmp3 = r.stop < num

    # outside of the range boundaries
    if not cmp2 or not cmp3:
        return False

    # num must be on a valid step inside the boundaries
    return (num - r.start) % r.step == 0

106
2018-05-06 15:41



@ ChrisResseling: Tôi nghĩ rằng đây là thông tin đủ khác nhau (và đủ của nó) rằng việc chỉnh sửa câu trả lời của Martijn sẽ không phù hợp ở đây. Đó là một cuộc gọi phán xét, nhưng mọi người thường sai lầm khi không thay đổi quyết liệt cho câu trả lời của người khác. - abarnert


Nếu bạn đang tự hỏi tại sao tối ưu hóa này đã được thêm vào range.__contains__và tại sao nó không phải thêm vào xrange.__contains__ trong 2,7:

Đầu tiên, như Ashwini Chaudhary đã khám phá ra, số phát hành 1766304 đã được mở một cách rõ ràng để tối ưu hóa [x]range.__contains__. Một bản vá cho điều này là được chấp nhận và đăng ký 3.2, nhưng không được quay trở lại 2.7 vì "xrange đã cư xử như thế này trong một thời gian dài đến mức tôi không thấy những gì nó mua chúng tôi để cam kết bản vá này muộn." (2.7 đã gần như ở thời điểm đó.)

Trong khi đó:

Ban đầu, xrange là một đối tượng không hoàn toàn. Như tài liệu 3.1 Nói:

Đối tượng dải ô có hành vi rất ít: chúng chỉ hỗ trợ lập chỉ mục, lặp lại và len chức năng.

Điều này không hoàn toàn đúng; một xrange đối tượng thực sự đã hỗ trợ một vài thứ khác tự động với lập chỉ mục và len,* kể cả __contains__ (thông qua tìm kiếm tuyến tính). Nhưng không ai nghĩ rằng nó là giá trị làm cho họ đầy đủ trình tự vào thời điểm đó.

Sau đó, như một phần của việc triển khai Lớp cơ sở trừu tượng PEP, điều quan trọng là phải tìm ra loại nội trang nào cần được đánh dấu là triển khai ABC, và xrange/range đã xác nhận để triển khai collections.Sequence, mặc dù nó vẫn chỉ xử lý cùng một "hành vi rất ít". Không ai nhận thấy vấn đề đó cho đến số phát hành 9213. Bản vá cho vấn đề đó không chỉ được thêm index và count đến 3.2 range, nó cũng làm việc lại tối ưu __contains__ (chia sẻ cùng một môn toán với indexvà được sử dụng trực tiếp bởi count).**  Thay đổi này đã đi vào cho 3,2 là tốt, và không phải là backported đến 2.x, bởi vì "đó là một bugfix cho biết thêm phương pháp mới". (Tại thời điểm này, 2.7 đã qua trạng thái rc.)

Vì vậy, có hai cơ hội để tối ưu hóa này trở lại 2.7, nhưng cả hai đều bị từ chối.


* Trong thực tế, bạn thậm chí có được lặp lại miễn phí với len và lập chỉ mục, nhưng trong 2.3  xrange các đối tượng có một trình vòng lặp tùy chỉnh. Mà sau đó họ bị mất trong 3.x, trong đó sử dụng cùng listiterator nhập như list.

** Phiên bản đầu tiên thực sự thực hiện lại và nhận được thông tin chi tiết sai — ví dụ: nó sẽ cung cấp cho bạn MyIntSubclass(2) in range(5) == False. Nhưng phiên bản cập nhật của bản vá của Daniel Stutzbach đã khôi phục hầu hết mã trước đó, bao gồm cả dự phòng chung, chậm _PySequence_IterSearch trước 3.2 range.__contains__ được sử dụng ngầm khi tối ưu hóa không áp dụng.


79
2018-05-06 21:42



Từ các ý kiến ​​ở đây: cải tiến xrange.__contains__, có vẻ như họ đã không đưa nó trở lại Python 2 chỉ để lại một yếu tố bất ngờ cho người dùng và đã quá muộn o_O. Các count và index  vá đã được thêm vào sau. Tệp tại thời điểm đó: hg.python.org/cpython/file/d599a3f2e72d/Objects/rangeobject.c - Ashwini Chaudhary
Tôi có một nghi ngờ nham hiểm rằng một số devs lõi python là một phần để "tình yêu khó khăn" cho python 2.x bởi vì họ muốn khuyến khích mọi người chuyển sang python3 cao cấp :) - wim
Ngoài ra tôi đặt cược đó là một gánh nặng rất lớn để có thêm các tính năng mới cho các phiên bản cũ. Hãy tưởng tượng nếu bạn đã đi đến Oracle và nói, "Hãy nhìn xem, tôi đang trên Java 1.4 và tôi xứng đáng với biểu thức lambda! Quay lại chúng không có gì." - Robert Grant
@ RickTeachey yeah nó chỉ là một ví dụ. Nếu tôi nói 1.7 nó vẫn sẽ được áp dụng. Đó là một sự khác biệt định lượng không phải là định tính. Về cơ bản các dev (chưa thanh toán) không thể tạo ra những thứ mới mẻ trong 3.x và backport nó thành 2.x cho những người không muốn nâng cấp. Đó là một gánh nặng khổng lồ và vô lý. Bạn có nghĩ rằng có gì đó không ổn với lý luận của tôi không? - Robert Grant
@ RickTeachey: 2,7 là từ 3,1 đến 3,2, không phải khoảng 3,3. Và điều đó có nghĩa là 2,7 đã được trong rc khi những thay đổi cuối cùng để 3.2 đã đi vào, mà làm cho các ý kiến ​​lỗi dễ hiểu hơn. Dù sao, tôi nghĩ rằng họ đã thực hiện một vài sai lầm trong hồi tưởng (đặc biệt là giả sử mọi người sẽ di chuyển qua 2to3 thay vì thông qua mã phiên bản kép với sự trợ giúp của các thư viện như six, đó là lý do tại sao chúng tôi có những thứ như dict.viewkeys mà không ai sẽ sử dụng), và có một vài thay đổi mà chỉ đến quá muộn trong 3.2, nhưng đối với hầu hết các phần 2.7 là một ấn bản "cuối cùng 2.x bao giờ" khá ấn tượng. - abarnert


Các câu trả lời khác đã giải thích rõ ràng, nhưng tôi muốn cung cấp một thử nghiệm khác minh họa bản chất của các đối tượng phạm vi:

>>> r = range(5)
>>> for i in r:
        print(i, 2 in r, list(r))

0 True [0, 1, 2, 3, 4]
1 True [0, 1, 2, 3, 4]
2 True [0, 1, 2, 3, 4]
3 True [0, 1, 2, 3, 4]
4 True [0, 1, 2, 3, 4]

Như bạn có thể thấy, một đối tượng phạm vi là một đối tượng nhớ phạm vi của nó và có thể được sử dụng nhiều lần (ngay cả khi đang lặp qua nó), không chỉ là trình tạo một lần.


35
2018-05-06 16:04



Tôi sẽ không chơi sự tương đồng với các đối tượng lát quá nhiều; nó có vẻ gây nhầm lẫn cho nhiều người hơn là giúp ích. (Khi ai đó nhận ra điều đó, họ bắt đầu tự hỏi tại sao có hai loại khác nhau ở nơi đầu tiên, và phải mất một chút để giải thích nó. Và sau đó một số người trong số họ đi và thiết kế các ngôn ngữ khác như Swift mà không bao giờ tìm ra, và chúng tôi nhận được tất cả các loại vấn đề gây phiền nhiễu mà họ hack qua lại cho 5 bản beta trước khi họ cuối cùng đã đưa ra một thiết kế hợp lý ...) - abarnert
@abarnert Bạn có nghĩ tôi nên xóa phần đó không? Tôi không bao giờ rõ ràng tạo ra các đối tượng slice anyway. Tôi chỉ cần thêm nó bởi vì chúng tương tự và ít nhất với tôi, nó cảm thấy trực quan hơn rằng các đối tượng slice là "tĩnh", vì dữ liệu không đến từ chúng mà từ chuỗi chúng được sử dụng. - Stefan Pochmann
Tôi nghĩ trực giác của bạn chắc chắn giúp làm cho nó rõ ràng hơn ... nhưng tôi không biết liệu bạn có thể làm việc đó trong câu trả lời hay không. Có thể bằng cách gọi một cách rõ ràng indices phương pháp? Nhưng tại thời điểm đó có lẽ nó nhận được quá xa điểm chính? - abarnert
@abarnert Nah, vì tôi chưa bao giờ sử dụng các đối tượng slice một cách rõ ràng, tôi thậm chí còn không biết phương pháp chỉ mục và tôi không cảm thấy muốn nghĩ về nó ngay bây giờ. Tôi sẽ loại bỏ phần đó khỏi câu trả lời. - Stefan Pochmann


Đó là tất cả về cách tiếp cận lười biếng để đánh giá và tối ưu hóa thêm một số range. Giá trị trong phạm vi không cần phải được tính toán cho đến khi sử dụng thực, hoặc thậm chí còn hơn do tối ưu hóa thêm.

Bằng cách số nguyên của bạn không lớn như vậy, hãy xem xét sys.maxsize

sys.maxsize in range(sys.maxsize)  khá nhanh

do tối ưu hóa - thật dễ dàng để so sánh số nguyên đã cho chỉ với min và max của dải ô.

nhưng:

float(sys.maxsize) in range(sys.maxsize)  khá chậm.

(trong trường hợp này không có tối ưu hóa trong range, vì vậy kể từ khi nhận được phao bất ngờ, python sẽ so sánh tất cả các số)


3
2018-03-16 10:47