Você está correto! 'example'[3:4]
e 'example'[3]
são fundamentalmente diferentes, e cortar fora dos limites de uma sequência (pelo menos para integrados) não causa um erro.
Pode ser surpreendente no início, mas faz sentido quando você pensa a respeito. A indexação retorna um único item, mas o fatiamento retorna uma subsequência de itens. Portanto, quando você tenta indexar um valor inexistente, não há nada a retornar. Mas quando você divide uma sequência fora dos limites, ainda pode retornar uma sequência vazia.
Parte do que é confuso aqui é que as strings se comportam de maneira um pouco diferente das listas. Veja o que acontece quando você faz a mesma coisa com uma lista:
>>> [0, 1, 2, 3, 4, 5][3]
3
>>> [0, 1, 2, 3, 4, 5][3:4]
[3]
Aqui, a diferença é óbvia. No caso de strings, os resultados parecem ser idênticos porque em Python não existe um caractere individual fora de uma string. Um único caractere é apenas uma string de 1 caractere.
(Para a semântica exata de fatiar fora do intervalo de uma sequência, consulte a resposta de mgilson .)
[999:9999]
não é um índice, é uma fatia e tem semânticas diferentes. Da introdução do python: "Índices de fatia degenerados são tratados com elegância: um índice muito grande é substituído pelo tamanho da string, um limite superior menor que o limite inferior retorna uma string vazia."