Pada dasarnya Anda memilih satu set bug off-by-one untuk bug lainnya. Satu set lebih cenderung menyebabkan loop Anda berakhir lebih awal, yang lain cenderung menyebabkan Pengecualian (atau buffer overflow dalam bahasa lain). Setelah Anda menulis banyak kode, Anda akan melihat bahwa pilihan perilaku lebih range()masuk akal
@unutbu Artikel Djikstra itu sering dikutip dalam topik ini tetapi tidak memberikan nilai apa pun di sini, orang menggunakannya hanya sebagai daya tarik bagi otoritas. Satu-satunya alasan semu yang relevan yang ia berikan untuk pertanyaan OP adalah ia kebetulan merasa bahwa termasuk batas atas menjadi "tidak wajar" dan "jelek" dalam kasus tertentu di mana urutan kosong - itu adalah posisi yang sepenuhnya subjektif dan mudah diperdebatkan, jadi itu tidak membawa banyak ke meja. "Percobaan" dengan Mesa tidak banyak nilainya baik tanpa mengetahui kendala khusus mereka atau metode evaluasi.
sundar - Reinstate Monica
6
@andreasdr Tetapi bahkan jika argumen kosmetik valid, bukankah pendekatan Python memperkenalkan masalah baru keterbacaan? Dalam bahasa Inggris yang umum digunakan, istilah "rentang" menyiratkan bahwa sesuatu berkisar dari sesuatu ke sesuatu - seperti interval. Itu len (daftar (rentang (1,2))) mengembalikan 1 dan len (daftar (kisaran (2))) mengembalikan 2 adalah sesuatu yang Anda benar-benar harus belajar untuk dicerna.
armin
Jawaban:
245
Karena itu lebih umum untuk memanggil range(0, 10)yang mengembalikan [0,1,2,3,4,5,6,7,8,9]yang berisi 10 elemen yang sama len(range(0, 10)). Ingat bahwa programmer lebih suka pengindeksan berbasis 0.
Pertimbangkan juga cuplikan kode umum berikut:
for i in range(len(li)):pass
Bisakah Anda melihat bahwa jika range()naik tepat len(li)bahwa ini akan bermasalah? Programmer akan perlu secara eksplisit mengurangi 1. ini juga mengikuti tren umum dari programmer lebih memilih for(int i = 0; i < 10; i++)lebih for(int i = 0; i <= 9; i++).
Jika Anda sering memanggil rentang dengan permulaan 1, Anda mungkin ingin mendefinisikan fungsi Anda sendiri:
Jika itu alasannya, bukankah parameternya range(start, count)?
Mark Ransom
3
@shogun Nilai awal default ke 0, yaitu range(10)setara dengan range(0, 10).
moinudin
4
Anda range1tidak akan bekerja dengan rentang yang memiliki ukuran langkah berbeda dari 1.
dimo414
6
Anda menjelaskan bahwa rentang (x) harus dimulai dengan 0 dan x akan menjadi "panjang rentang". BAIK. Tetapi Anda tidak menjelaskan mengapa rentang (x, y) harus dimulai dengan x dan diakhiri dengan y-1. Jika programmer menginginkan for-loop dengan saya mulai dari 1 hingga 3, ia harus secara eksplisit menambahkan 1. Apakah ini benar-benar tentang kenyamanan?
armin
7
for i in range(len(li)):lebih merupakan antipattern. Orang harus menggunakan enumerate.
hans
27
Meskipun ada beberapa penjelasan algoritmik yang berguna di sini, saya pikir mungkin membantu untuk menambahkan beberapa alasan 'kehidupan nyata' yang sederhana tentang mengapa ia bekerja dengan cara ini, yang menurut saya berguna ketika memperkenalkan subjek tersebut kepada pendatang baru:
Dengan sesuatu seperti 'rentang (1,10)' kebingungan dapat timbul dari pemikiran bahwa sepasang parameter mewakili "awal dan akhir".
Ini sebenarnya mulai dan "berhenti".
Sekarang, jika itu adalah nilai "akhir" maka, ya, Anda mungkin berharap nomor itu akan dimasukkan sebagai entri terakhir dalam urutan. Tapi itu bukan "akhir".
Yang lain secara keliru menyebut parameter itu "menghitung" karena jika Anda hanya menggunakan 'rentang (n)' maka itu tentu saja, ulangi 'n' kali. Logika ini rusak ketika Anda menambahkan parameter awal.
Jadi intinya adalah mengingat namanya: " stop ". Itu berarti itu adalah titik di mana, ketika tercapai, iterasi akan segera berhenti. Tidak setelah titik itu.
Jadi, sementara "mulai" memang mewakili nilai pertama yang akan dimasukkan, pada mencapai nilai "berhenti" itu 'rusak' daripada terus memproses 'yang juga' sebelum berhenti.
Satu analogi yang saya gunakan dalam menjelaskan ini kepada anak-anak adalah, ironisnya, itu lebih baik daripada anak-anak! Itu tidak berhenti setelah seharusnya - berhenti segera tanpa menyelesaikan apa yang dilakukannya. (Mereka mendapatkan ini;))
Analogi lain - ketika Anda mengendarai mobil Anda tidak melewati tanda berhenti / menyerah / 'memberi jalan' dan berakhir dengan itu duduk di suatu tempat di sebelah, atau di belakang, mobil Anda. Secara teknis Anda masih belum mencapainya ketika Anda berhenti. Itu tidak termasuk dalam 'hal-hal yang Anda lewati dalam perjalanan Anda'.
Saya harap beberapa dari itu membantu dalam menjelaskan kepada Pythonitos / Pythonitas!
Anda mencoba memberi lipstik pada babi. Perbedaan antara "berhenti" dan "berakhir" tidak masuk akal. Jika saya beralih dari 1 ke 7, saya belum lulus 7. Ini hanyalah cacat Python untuk memiliki konvensi yang berbeda untuk posisi start dan stop. Dalam bahasa lain, termasuk bahasa manusia, "dari X ke Y" berarti "dari X ke Y". Dalam Python, "X: Y" berarti "X: Y-1". Jika Anda mengadakan rapat dari 9 hingga 11, apakah Anda memberi tahu orang-orang itu dari 9 hingga 12, atau dari 8 hingga 11?
bzip2
24
Kisaran eksklusif memang memiliki beberapa manfaat:
Untuk satu hal, setiap item dalam range(0,n)adalah indeks yang valid untuk daftar panjang n.
Juga range(0,n)memiliki panjang n, bukan n+1kisaran inklusif.
Ini bekerja dengan baik dalam kombinasi dengan pengindeksan berbasis nol dan len(). Misalnya, jika Anda memiliki 10 item dalam daftar x, mereka diberi nomor 0-9. range(len(x))memberi Anda 0-9.
Tentu saja, orang akan mengatakan kepada Anda bahwa itu lebih Pythonic untuk dilakukan for item in xatau for index, item in enumerate(x)bukan for i in range(len(x)).
Mengiris juga berfungsi: foo[1:4]adalah item 1-3 dari foo(ingat bahwa item 1 sebenarnya item kedua karena pengindeksan berbasis nol). Untuk konsistensi, keduanya harus bekerja dengan cara yang sama.
Saya menganggapnya sebagai: "angka pertama yang Anda inginkan, diikuti oleh angka pertama yang tidak Anda inginkan." Jika Anda ingin 1-10, angka pertama yang tidak Anda inginkan adalah 11, jadi begitu range(1, 11).
Jika menjadi rumit dalam aplikasi tertentu, cukup mudah untuk menulis fungsi pembantu kecil yang menambahkan 1 ke indeks akhir dan panggilan range().
Setuju untuk mengiris. 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
nobar
mungkin contoh enumerate harus dibaca for index, item in enumerate(x)untuk menghindari kebingungan
seans
@seans Terima kasih, sudah diperbaiki.
ramah
12
Ini juga berguna untuk rentang pemisahan; range(a,b)dapat dibagi menjadi range(a, x)dan range(x, b), sedangkan dengan rentang inklusif Anda akan menulis salah satu x-1atau x+1. Meskipun Anda jarang perlu membagi rentang, Anda cenderung untuk membagi daftar cukup sering, yang merupakan salah satu alasan mengiris daftar l[a:b]termasuk elemen a tapi bukan b. Kemudian rangememiliki properti yang sama membuatnya konsisten.
Gah! Saya tidak menggunakan Ruby, tetapi saya bisa membayangkan bahwa 1..10vs 1...10sulit dibedakan antara ketika membaca kode!
moinudin
6
@marcog - ketika Anda tahu dua bentuk ada mata Anda selaras dengan perbedaan :)
Skilldrick
11
Operator jangkauan Ruby sangat intuitif. Bentuk yang lebih panjang memberi Anda urutan yang lebih pendek. batuk
Russell Borogove
4
@Russell, mungkin 1 ............ 20 harus memberikan rentang yang sama dengan 1..10. Sekarang itu akan menjadi gula sintaksis yang berharga untuk ditukar. ;)
kevpie
4
@Russell Titik ekstra meremas item terakhir di luar jangkauan :)
Skilldrick
5
Pada dasarnya dalam python range(n)iterates ntimes, yang bersifat eksklusif itulah sebabnya ia tidak memberikan nilai terakhir ketika sedang dicetak, kita dapat membuat fungsi yang memberikan nilai inklusif artinya juga akan mencetak nilai terakhir yang disebutkan dalam range.
def main():for i in inclusive_range(25):print(i, sep=" ")def inclusive_range(*args):
numargs = len(args)if numargs ==0:raiseTypeError("you need to write at least a value")elif numargs ==1:
stop = args[0]
start =0
step =1elif numargs ==2:(start, stop)= args
step =1elif numargs ==3:(start, stop, step)= args
else:raiseTypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
i = start
while i <= stop:yield i
i += step
if __name__ =="__main__":
main()
Dalam banyak kasus hanya lebih mudah untuk dipikirkan.
Pada dasarnya, kita bisa menganggap rentang sebagai interval antara startdan end. Jika start <= end, panjang interval di antara mereka adalah end - start. Jika lensebenarnya didefinisikan sebagai panjang, Anda harus:
len(range(start, end))== start - end
Namun, kami menghitung bilangan bulat yang termasuk dalam rentang alih-alih mengukur panjang interval. Agar properti di atas benar, kita harus memasukkan salah satu titik akhir dan mengecualikan yang lain.
Menambahkan stepparameter seperti memperkenalkan satuan panjang. Dalam hal ini, yang Anda harapkan
len(range(start, end, step))==(start - end)/ step
panjangnya. Untuk mendapatkan penghitungan, Anda hanya menggunakan divisi integer.
Pertahanan dari ketidakkonsistenan Python ini lucu. Jika saya ingin interval antara dua angka, mengapa saya menggunakan pengurangan untuk mendapatkan perbedaan daripada interval? Tidak konsisten untuk menggunakan konvensi pengindeksan yang berbeda untuk posisi awal dan akhir. Mengapa Anda perlu menulis "5:22" untuk mendapatkan posisi 5 hingga 21?
bzip2
Ini bukan Python, itu cukup umum di seluruh papan. Di C, Java, Ruby, sebut saja
Arseny
Saya bermaksud mengatakan bahwa itu umum untuk pengindeksan, bukan karena bahasa lain memiliki jenis objek yang persis sama
range()
masuk akalJawaban:
Karena itu lebih umum untuk memanggil
range(0, 10)
yang mengembalikan[0,1,2,3,4,5,6,7,8,9]
yang berisi 10 elemen yang samalen(range(0, 10))
. Ingat bahwa programmer lebih suka pengindeksan berbasis 0.Pertimbangkan juga cuplikan kode umum berikut:
Bisakah Anda melihat bahwa jika
range()
naik tepatlen(li)
bahwa ini akan bermasalah? Programmer akan perlu secara eksplisit mengurangi 1. ini juga mengikuti tren umum dari programmer lebih memilihfor(int i = 0; i < 10; i++)
lebihfor(int i = 0; i <= 9; i++)
.Jika Anda sering memanggil rentang dengan permulaan 1, Anda mungkin ingin mendefinisikan fungsi Anda sendiri:
sumber
range(start, count)
?range(10)
setara denganrange(0, 10)
.range1
tidak akan bekerja dengan rentang yang memiliki ukuran langkah berbeda dari1
.for i in range(len(li)):
lebih merupakan antipattern. Orang harus menggunakanenumerate
.Meskipun ada beberapa penjelasan algoritmik yang berguna di sini, saya pikir mungkin membantu untuk menambahkan beberapa alasan 'kehidupan nyata' yang sederhana tentang mengapa ia bekerja dengan cara ini, yang menurut saya berguna ketika memperkenalkan subjek tersebut kepada pendatang baru:
Dengan sesuatu seperti 'rentang (1,10)' kebingungan dapat timbul dari pemikiran bahwa sepasang parameter mewakili "awal dan akhir".
Ini sebenarnya mulai dan "berhenti".
Sekarang, jika itu adalah nilai "akhir" maka, ya, Anda mungkin berharap nomor itu akan dimasukkan sebagai entri terakhir dalam urutan. Tapi itu bukan "akhir".
Yang lain secara keliru menyebut parameter itu "menghitung" karena jika Anda hanya menggunakan 'rentang (n)' maka itu tentu saja, ulangi 'n' kali. Logika ini rusak ketika Anda menambahkan parameter awal.
Jadi intinya adalah mengingat namanya: " stop ". Itu berarti itu adalah titik di mana, ketika tercapai, iterasi akan segera berhenti. Tidak setelah titik itu.
Jadi, sementara "mulai" memang mewakili nilai pertama yang akan dimasukkan, pada mencapai nilai "berhenti" itu 'rusak' daripada terus memproses 'yang juga' sebelum berhenti.
Satu analogi yang saya gunakan dalam menjelaskan ini kepada anak-anak adalah, ironisnya, itu lebih baik daripada anak-anak! Itu tidak berhenti setelah seharusnya - berhenti segera tanpa menyelesaikan apa yang dilakukannya. (Mereka mendapatkan ini;))
Analogi lain - ketika Anda mengendarai mobil Anda tidak melewati tanda berhenti / menyerah / 'memberi jalan' dan berakhir dengan itu duduk di suatu tempat di sebelah, atau di belakang, mobil Anda. Secara teknis Anda masih belum mencapainya ketika Anda berhenti. Itu tidak termasuk dalam 'hal-hal yang Anda lewati dalam perjalanan Anda'.
Saya harap beberapa dari itu membantu dalam menjelaskan kepada Pythonitos / Pythonitas!
sumber
Kisaran eksklusif memang memiliki beberapa manfaat:
Untuk satu hal, setiap item dalam
range(0,n)
adalah indeks yang valid untuk daftar panjangn
.Juga
range(0,n)
memiliki panjangn
, bukann+1
kisaran inklusif.sumber
Ini bekerja dengan baik dalam kombinasi dengan pengindeksan berbasis nol dan
len()
. Misalnya, jika Anda memiliki 10 item dalam daftarx
, mereka diberi nomor 0-9.range(len(x))
memberi Anda 0-9.Tentu saja, orang akan mengatakan kepada Anda bahwa itu lebih Pythonic untuk dilakukan
for item in x
ataufor index, item in enumerate(x)
bukanfor i in range(len(x))
.Mengiris juga berfungsi:
foo[1:4]
adalah item 1-3 darifoo
(ingat bahwa item 1 sebenarnya item kedua karena pengindeksan berbasis nol). Untuk konsistensi, keduanya harus bekerja dengan cara yang sama.Saya menganggapnya sebagai: "angka pertama yang Anda inginkan, diikuti oleh angka pertama yang tidak Anda inginkan." Jika Anda ingin 1-10, angka pertama yang tidak Anda inginkan adalah 11, jadi begitu
range(1, 11)
.Jika menjadi rumit dalam aplikasi tertentu, cukup mudah untuk menulis fungsi pembantu kecil yang menambahkan 1 ke indeks akhir dan panggilan
range()
.sumber
w = 'abc'; w[:] == w[0:len(w)]; w[:-1] == w[0:len(w)-1];
def full_range(start,stop): return range(start,stop+1) ## helper function
for index, item in enumerate(x)
untuk menghindari kebingunganIni juga berguna untuk rentang pemisahan;
range(a,b)
dapat dibagi menjadirange(a, x)
danrange(x, b)
, sedangkan dengan rentang inklusif Anda akan menulis salah satux-1
ataux+1
. Meskipun Anda jarang perlu membagi rentang, Anda cenderung untuk membagi daftar cukup sering, yang merupakan salah satu alasan mengiris daftarl[a:b]
termasuk elemen a tapi bukan b. Kemudianrange
memiliki properti yang sama membuatnya konsisten.sumber
Panjang rentang adalah nilai teratas dikurangi nilai bawah.
Ini sangat mirip dengan sesuatu seperti:
dalam bahasa gaya-C.
Juga menyukai kisaran Ruby:
Namun, Ruby mengakui bahwa berkali-kali Anda ingin memasukkan nilai terminal dan menawarkan sintaks alternatif:
sumber
1..10
vs1...10
sulit dibedakan antara ketika membaca kode!Pada dasarnya dalam python
range(n)
iteratesn
times, yang bersifat eksklusif itulah sebabnya ia tidak memberikan nilai terakhir ketika sedang dicetak, kita dapat membuat fungsi yang memberikan nilai inklusif artinya juga akan mencetak nilai terakhir yang disebutkan dalam range.sumber
Pertimbangkan kodenya
Idenya adalah bahwa Anda mendapatkan daftar panjang
y-x
, yang Anda dapat (seperti yang Anda lihat di atas) beralih.Baca pada python docs untuk rentang - mereka menganggap for-loop iteration sebagai usecase utama.
sumber
Dalam banyak kasus hanya lebih mudah untuk dipikirkan.
Pada dasarnya, kita bisa menganggap rentang sebagai interval antara
start
danend
. Jikastart <= end
, panjang interval di antara mereka adalahend - start
. Jikalen
sebenarnya didefinisikan sebagai panjang, Anda harus:Namun, kami menghitung bilangan bulat yang termasuk dalam rentang alih-alih mengukur panjang interval. Agar properti di atas benar, kita harus memasukkan salah satu titik akhir dan mengecualikan yang lain.
Menambahkan
step
parameter seperti memperkenalkan satuan panjang. Dalam hal ini, yang Anda harapkanpanjangnya. Untuk mendapatkan penghitungan, Anda hanya menggunakan divisi integer.
sumber