> It seems altogether surprising that with an empty list or tuple a, a[1] results in index error, yet a[1:] quietly returns an empty list or tuple.
`a[1:]` returns the sequence of elements that start at index 1. If there is no such element, the list is empty. I don’t see any good reason why this should throw an error.
Both cases are an index error. It's just for some other reasons in case of the section, the error is represented by an empty object and it's left to user to handle the result.
This could easily conceal the indexing error unless the caller code explicitly checks the length of the returned section.
> This could easily conceal the indexing error unless the caller code explicitly checks the length of the returned section.
An empty returned section doesn’t mean the index was out of bounds (`a[0:0]`); if you want to make sure you have to check the length before slicing, like in Go.
When you slice a list, you get a list.
When you see there is nothing inside the returning list, you know that means end of list, contains zero element.
Slicing and indexing return object at different level.
This should signal an explicit error, which invalid index is indeed. If user believes for some reason the invalid indexing is ok, then it could be caught and handled. No ambiguity.
I think it is consistent, it works a bit like filtering an element from a mathematical set.
Given a set of sheeps, let x be the five-legged sheep is inconsistent because we know neither the existence or uniqueness of shuch sheep, so it raises an exception.
Given a set of sheeps, let x be the subset of five legged sheeps is the empty set because there is no such sheep.
but this may also just be because I internalised Python's behavior.
Some language have a specific value to denote the first thing, for example:
["a", "b", "c"][4]
gives `undefined` in JavaScript but it differs from `null` which would be the equivalent to `None` in Python (and I don't think Python has such concept).
a[1] has to raise an IndexError because there's no return value it could use to otherwise communicate the item doesn't exist. Any such value could itself be a member of the sequence. To behave otherwise, Python would have to define a sentinel value that isn't allowed to be a member of a sequence.
When using slice notation, the return value is a sequence, so returning a zero-length sequence is sufficient to communicate you asked for more items than exist.
It may be surprising, but it almost always leads to more ergonomic code.