La funzione della quale parlate si chiama "slice", ed è una funzione peculiare di Python.
Lo "slicing" può essere effettuato su qualsiasi elemento Iterabile (Array, Stringhe, Tuple ecc...) e restituisce una "parte" (al più tutto) dell'oggetto di partenza.
La sintassi è la seguente: object[start:stop:step], dove "start" indica l'indice dal quale iniziare lo slice (indice incluso), "stop" indica l'indice dove terminare lo slice (indice escluso. L'ultimo elemento preso sarà dunque indice-1) e "step" indica gli eventuali salti di indice tra un elemento e l'altro (di default step vale 1, e vengono presi dunque tutti gli elementi tra start e stop-1. In caso fosse 2, verrebbe preso un elemento si e uno no.).
Alcuni parametri possono essere omessi o (come ha già sottolineato Denis) negativi, alterando il risultato. Nello specifico:
- Omettendo "start" lo slice inizia dall'indice 0 (analogamente omettendo lo "stop" lo slice termina all'ultimo elemento)
- Omettendo lo "step" si utilizza il default step 1, prendendo di conseguenza tutti gli elementi tra lo start e lo stop
- Dando uno "start" negativo lo slice inizierà da N elementi prima della fine dell'elemento iterabile (analogamente dando uno "stop" negativo lo slice terminerà N elementi prima della fine)
- Dando un "step" negativo "l'estrazione" (non è una vera e propria estrazione in quanto l'oggetto di partenza non viene toccato, al contrario di funzioni come "pop" o "del") avverrà in ordine inverso.
Solitamente quando si deve "ribaltare" un'iterabile il metodo più semplice è dunque utilizzare uno step negativo:
object[::-1]
Vi consiglio di "giocare" un po' con la funzione slice osservando come cambia il risultato a seconda degli indici che si inseriscono, in quanto è veramente una funzione importantissima!
Potete trovare la documentazione ufficiale di questa e altre "built-in functions" qui:
https://docs.python.org/3/library/functions.html#slice