Python synergies in list addressing

Something I took from this Ian Bicking entry is that synergies and elegance don't just happen; someone usually worked hard to make it all come out neatly. Python lists have an interesting case of this; some apparently odd decisions in other places turn out to be needed to create useful (and error-avoiding ) synergies.

Python lists (and sequences in general) are indexed from 0. Zero-based indexing presents a problem, which can be succinctly stated as this: list element indexes run from 0 to len(lst) -1 . That -1 is ugly and error prone.

So Python has quietly arranged things so that you never have to write it (or +1 , its kissing cousin), by making 'slice' addressing of lists and range() end-exclusive asymmetric (instead of ' i:j ' running from i to j , it runs from i to j-1 ). This means:

  • range(len(lst)) generates indexes that exactly cover the list, since they run from 0 to len(lst)-1 .
  • lst[:len(lst)] is the entire list.
  • if pref is at the start of the list, lst[len(pref):] is the list with pref removed from the start.
  • if sublst is in lst starting at pos , lst[:pos] is everything before sublst and lst[pos + len(sublst):] is everything after sublst .
  • if suf is at the end of lst , lst[:-len(suf)] is the list with suf removed from the end.
  • avoiding a subtler error, lst[len(pref):] works even if pref is zero length (although lst[:-len(suf)] does not; can't win them all).

All of these are straightforward expressions, with nary a stray -1 or +1 in sight and no chance for off by one errors. (Is it elegant or does the asymmetry cancel out the lack of +1 and -1 ? That's in the eye of the beholder, but I like it.)

Python is not the first language to notice this issue; Scheme's substring is end-exclusive, for example. Other things duck the issue by having their substring operation take a start and a length, instead of a start and and end position.

(This entry's genesis came from comments made on AClosureConfusion .)