Tại sao phạm vi (bắt đầu, kết thúc) không bao gồm kết thúc?


305
>>> range(1,11)

mang đến cho bạn

[1,2,3,4,5,6,7,8,9,10]

Tại sao không phải là 1-11?

Họ chỉ quyết định làm điều đó một cách ngẫu nhiên hay nó có một số giá trị mà tôi không thấy?


11
đọc Dijkstra, ewd831
SilentGhost

11
Về cơ bản, bạn đang chọn một bộ lỗi khác nhau. Một bộ có nhiều khả năng khiến các vòng lặp của bạn kết thúc sớm, bộ kia có khả năng gây ra Ngoại lệ (hoặc tràn bộ đệm trong các ngôn ngữ khác). Khi bạn đã viết một loạt mã, bạn sẽ thấy rằng sự lựa chọn hành vi range()có ý nghĩa thường xuyên hơn nhiều
John La Rooy

32
Liên kết đến Dijkstra, ewd831: cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF
unutbu

35
@unutbu Bài báo Djikstra được trích dẫn trong chủ đề này nhưng không cung cấp gì về giá trị ở đây, mọi người sử dụng nó như một lời kêu gọi chính quyền. Lý do giả duy nhất có liên quan mà anh đưa ra cho câu hỏi của OP là anh tình cờ cảm thấy rằng bao gồm cả giới hạn trên trở nên "không tự nhiên" và "xấu xí" trong trường hợp cụ thể khi trình tự trống rỗng - đó là một vị trí hoàn toàn chủ quan và dễ dàng tranh cãi, Vì vậy, nó không mang lại nhiều cho bàn. "Thử nghiệm" với Mesa không có nhiều giá trị mà không cần biết các ràng buộc hoặc phương pháp đánh giá cụ thể của họ.
- Phục hồi Monica

6
@andreasdr Nhưng ngay cả khi đối số mỹ phẩm là hợp lệ, cách tiếp cận của Python không đưa ra một vấn đề mới về khả năng đọc? Trong tiếng Anh thông dụng, thuật ngữ "phạm vi" ngụ ý rằng một cái gì đó dao động từ một cái gì đó đến một cái gì đó - như một khoảng. Mà len (danh sách (phạm vi (1,2))) trả về 1 và len (danh sách (phạm vi (2))) trả về 2 là điều bạn thực sự phải học để tiêu hóa.
armin

Câu trả lời:


245

Bởi vì nó phổ biến hơn để gọi range(0, 10)trả về [0,1,2,3,4,5,6,7,8,9]trong đó có 10 phần tử bằng nhau len(range(0, 10)). Hãy nhớ rằng các lập trình viên thích lập chỉ mục dựa trên 0.

Ngoài ra, hãy xem xét đoạn mã phổ biến sau:

for i in range(len(li)):
    pass

Bạn có thể thấy rằng nếu range()đi lên chính xác len(li)thì điều này sẽ có vấn đề? Các lập trình viên sẽ cần phải trừ một cách rõ ràng 1. Điều này cũng theo xu hướng chung của các lập trình viên thích for(int i = 0; i < 10; i++)hơn for(int i = 0; i <= 9; i++).

Nếu bạn đang gọi phạm vi bắt đầu bằng 1 thường xuyên, bạn có thể muốn xác định chức năng của riêng mình:

>>> def range1(start, end):
...     return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

48
Nếu đó là lý do thì các tham số sẽ range(start, count)không?
Đánh dấu tiền chuộc

3
@shogun Giá trị bắt đầu mặc định là 0, nghĩa range(10)là tương đương với range(0, 10).
moinudin

4
Bạn range1sẽ không làm việc với các phạm vi có kích thước bước khác 1.
dimo414

6
Bạn giải thích rằng phạm vi (x) nên bắt đầu bằng 0 và x sẽ là "độ dài của phạm vi". ĐỒNG Ý. Nhưng bạn đã không giải thích tại sao phạm vi (x, y) nên bắt đầu bằng x và kết thúc bằng y-1. Nếu lập trình viên muốn một vòng lặp for với i dao động từ 1 đến 3, anh ta phải thêm rõ ràng 1. Đây có thực sự là về sự tiện lợi?
armin

7
for i in range(len(li)):đúng hơn là một antipotype. Người ta nên sử dụng enumerate.
Hans

27

Mặc dù có một số giải thích thuật toán hữu ích ở đây, tôi nghĩ rằng nó có thể giúp thêm một số lý do đơn giản 'đời thực' vào lý do tại sao nó hoạt động theo cách này, điều mà tôi thấy hữu ích khi giới thiệu chủ đề này cho những người mới đến:

Với một cái gì đó như 'phạm vi (1,10)' có thể nảy sinh từ suy nghĩ rằng cặp tham số đại diện cho "bắt đầu và kết thúc".

Nó thực sự bắt đầu và "dừng lại".

Bây giờ, nếu đó giá trị "kết thúc" thì, vâng, bạn có thể mong đợi số đó sẽ được đưa vào làm mục cuối cùng trong chuỗi. Nhưng nó không phải là "kết thúc".

Những người khác gọi nhầm tham số đó là "đếm" bởi vì nếu bạn chỉ sử dụng 'phạm vi (n)' thì dĩ nhiên, nó sẽ lặp đi lặp lại 'n' lần. Logic này bị hỏng khi bạn thêm tham số bắt đầu.

Vì vậy, điểm quan trọng là nhớ tên của nó: " dừng lại ". Điều đó có nghĩa là nó là điểm mà khi đạt tới, việc lặp lại sẽ dừng ngay lập tức. Không phải sau thời điểm đó.

Vì vậy, mặc dù "bắt đầu" thực sự đại diện cho giá trị đầu tiên được đưa vào, nhưng khi đạt đến giá trị "dừng", nó sẽ "phá vỡ" thay vì tiếp tục xử lý "giá trị đó" trước khi dừng.

Một điểm tương tự mà tôi đã sử dụng để giải thích điều này với trẻ em là, trớ trêu thay, nó lại cư xử tốt hơn trẻ em! Nó không dừng lại sau khi nó được yêu cầu - nó dừng lại ngay lập tức mà không hoàn thành những gì nó đang làm. (Họ hiểu điều này;))

Một sự tương tự khác - khi bạn lái xe ô tô, bạn không vượt qua điểm dừng / nhường / 'nhường đường' và kết thúc bằng việc ngồi ở đâu đó bên cạnh hoặc phía sau xe của bạn. Về mặt kỹ thuật bạn vẫn chưa đạt được nó khi bạn dừng lại. Nó không được bao gồm trong 'những điều bạn đã vượt qua trên hành trình của mình'.

Tôi hy vọng một số điều đó sẽ giúp giải thích cho Pythonitos / Pythonitas!


Giải thích này trực quan hơn. Cảm ơn
Fred

Những lời giải thích của trẻ em chỉ là vui nhộn!
Antony Hatchkins

1
Bạn đang cố gắng đặt son môi lên một con lợn. Sự phân biệt giữa "dừng" và "kết thúc" là vô lý. Nếu tôi đi từ 1 đến 7, tôi đã không vượt qua 7. Nó chỉ đơn giản là một lỗ hổng của Python để có các quy ước khác nhau cho các vị trí bắt đầu và dừng. Trong các ngôn ngữ khác, bao gồm cả ngôn ngữ của con người, "từ X đến Y" có nghĩa là "từ X đến Y". Trong Python, "X: Y" có nghĩa là "X: Y-1". Nếu bạn có một cuộc họp từ 9 đến 11, bạn có nói với mọi người rằng đó là từ 9 đến 12, hoặc từ 8 đến 11 không?
bzip2

24

Phạm vi độc quyền có một số lợi ích:

Đối với một điều, mỗi mục trong range(0,n)là một chỉ mục hợp lệ cho danh sách độ dài n.

Cũng range(0,n)có độ dài n, không phải n+1là một phạm vi bao gồm.


18

Nó hoạt động tốt kết hợp với lập chỉ mục dựa trên không và len(). Ví dụ: nếu bạn có 10 mục trong danh sách x, chúng được đánh số 0-9. range(len(x))cho bạn 0-9.

Tất nhiên, mọi người sẽ nói với bạn rằng đó là nhiều Pythonic để làm for item in xhay for index, item in enumerate(x)hơn là for i in range(len(x)).

Cắt lát cũng hoạt động theo cách đó: foo[1:4]là các mục 1-3 của foo(lưu ý rằng mục 1 thực sự là mục thứ hai do lập chỉ mục dựa trên số không). Để thống nhất, cả hai nên làm việc theo cùng một cách.

Tôi nghĩ về nó như sau: "số đầu tiên bạn muốn, tiếp theo là số đầu tiên bạn không muốn." Nếu bạn muốn 1-10, số đầu tiên bạn không muốn là 11, vì vậy nó là range(1, 11).

Nếu nó trở nên cồng kềnh trong một ứng dụng cụ thể, thật dễ dàng để viết một hàm trợ giúp nhỏ thêm 1 vào chỉ mục kết thúc và các cuộc gọi range().


1
Đồng ý về việc cắt lát. w = 'abc'; w[:] == w[0:len(w)]; w[:-1] == w[0:len(w)-1];
kevpie

def full_range(start,stop): return range(start,stop+1) ## helper function
tộc

có lẽ ví dụ liệt kê nên đọc for index, item in enumerate(x)để tránh nhầm lẫn
nghĩa là

@seans Cảm ơn, đã sửa.
kindall

12

Nó cũng hữu ích để phân chia phạm vi; range(a,b)có thể được chia thành range(a, x)range(x, b), trong khi với phạm vi bao gồm bạn sẽ viết x-1hoặc x+1. Mặc dù bạn hiếm khi cần phân chia phạm vi, nhưng bạn có xu hướng phân chia danh sách khá thường xuyên, đó là một trong những lý do cắt một danh sách l[a:b]bao gồm phần tử thứ a nhưng không phải là phần b. Sau đó rangecó cùng một tài sản làm cho nó phù hợp độc đáo.


11

Độ dài của phạm vi là giá trị hàng đầu trừ đi giá trị dưới cùng.

Nó rất giống với một cái gì đó như:

for (var i = 1; i < 11; i++) {
    //i goes from 1 to 10 in here
}

trong một ngôn ngữ kiểu C.

Cũng giống như phạm vi của Ruby:

1...11 #this is a range from 1 to 10

Tuy nhiên, Ruby nhận ra rằng nhiều lần bạn sẽ muốn bao gồm giá trị đầu cuối và cung cấp cú pháp thay thế:

1..10 #this is also a range from 1 to 10

17
Trời ạ! Tôi không sử dụng Ruby, nhưng tôi có thể tưởng tượng rằng 1..10vs 1...10là khó để phân biệt giữa khi đọc mã!
moinudin

6
@marcog - khi bạn biết hai hình thức tồn tại, đôi mắt của bạn sẽ có sự khác biệt :)
Skilldrick

11
Toán tử phạm vi của Ruby là hoàn toàn trực quan. Các hình thức dài hơn giúp bạn trình tự ngắn hơn. ho
Russell Borogove

4
@Russell, có thể 1 ............ 20 nên cho cùng phạm vi là 1..10. Bây giờ sẽ là một số đường cú pháp đáng chuyển đổi cho. ;)
kevpie

4
@Russell Dấu chấm bổ sung bóp mục cuối cùng trong phạm vi :)
Skilldrick

5

Về cơ bản trong python range(n)lặp đi lặp lại n, đó là bản chất độc quyền đó là lý do tại sao nó không đưa ra giá trị cuối cùng khi nó được in, chúng ta có thể tạo một hàm mang lại giá trị bao gồm, nó cũng sẽ in giá trị cuối cùng được đề cập trong phạm vi.

def main():
    for i in inclusive_range(25):
        print(i, sep=" ")


def inclusive_range(*args):
    numargs = len(args)
    if numargs == 0:
        raise TypeError("you need to write at least a value")
    elif numargs == 1:
        stop = args[0]
        start = 0
        step = 1
    elif numargs == 2:
        (start, stop) = args
        step = 1
    elif numargs == 3:
        (start, stop, step) = args
    else:
        raise TypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
    i = start
    while i <= stop:
        yield i
        i += step


if __name__ == "__main__":
    main()

4

Xem xét mã

for i in range(10):
    print "You'll see this 10 times", i

Ý tưởng là bạn có được một danh sách độ dài y-xmà bạn có thể (như bạn thấy ở trên) lặp đi lặp lại.

Đọc các tài liệu python cho phạm vi - họ xem xét lặp vòng lặp for usecase chính.


1

Nó chỉ thuận tiện hơn để lý do trong nhiều trường hợp.

Về cơ bản, chúng ta có thể nghĩ về một phạm vi như một khoảng giữa startend. Nếu start <= end, độ dài của khoảng giữa chúng là end - start. Nếu lenthực sự được định nghĩa là độ dài, bạn sẽ có:

len(range(start, end)) == start - end

Tuy nhiên, chúng tôi đếm các số nguyên có trong phạm vi thay vì đo độ dài của khoảng. Để giữ cho thuộc tính trên đúng, chúng ta nên bao gồm một trong các điểm cuối và loại trừ các điểm cuối khác.

Thêm steptham số giống như giới thiệu một đơn vị chiều dài. Trong trường hợp đó, bạn mong đợi

len(range(start, end, step)) == (start - end) / step

cho chiều dài. Để có được số đếm, bạn chỉ cần sử dụng phép chia số nguyên.


Những sự bảo vệ cho sự không nhất quán của Python rất vui nhộn. Nếu tôi muốn khoảng giữa hai số, tại sao tôi lại sử dụng phép trừ để lấy chênh lệch thay vì khoảng? Không nhất quán khi sử dụng các quy ước lập chỉ mục khác nhau cho các vị trí bắt đầu và kết thúc. Tại sao bạn cần phải viết "5:22" để có được vị trí 5 đến 21?
bzip2

Nó không phải của Python, nó khá phổ biến trên bảng. Trong C, Java, Ruby, bạn đặt tên cho nó
Asen

Tôi muốn nói rằng nó phổ biến cho việc lập chỉ mục, không phải các ngôn ngữ khác nhất thiết phải có cùng một loại đối tượng chính xác
Arseny
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.