Przeglądając jakiś rzeczywisty kod w języku Python można często natknąć się na nietypowe wykorzystanie operatora nawiasów kwadratowych. Tradycyjnie znaki te służą do indeksowania tablic, co w językach kompilowanych bezpośrednio do kodu maszynowego równa się prostej operacji na wskaźnikach:
Ponieważ jednak Python nie jest takim językiem, jego twórcy pozwolili sobie na to, by zawarte w nim kilogramy warstw abstrakcji oferowały dodatkową funkcjonalność również przy tak trywialnym zagadnieniu. W rezultacie indeksowanie tablic (a właściwie list, w tym i łańcuchów znaków) jest tu operacją, która często ukrywa w sobie znacznie bardziej skomplikowaną logikę niż to widać na pierwszy rzut oka.
Zacznijmy od tego, że w dopuszczalnymi indeksami są nie tylko dodatnie, ale i ujemne liczby całkowite. Oznaczają one dostęp do końcowych elementów tablicy: -1
do pierwszego od końca, -2
do drugiego, i tak dalej. Być może nie wydaje się logiczne to, że elementy tab[0]
i tab[-1]
są na przeciwnych krańcach listy podczas gdy ich indeksy różnią zaledwie o jeden. Uzasadnieniem jest tu odniesienie do indeksowania od końca w innych językach, czyli tab[tab.length() - i]
. W Pythonie po prostu pomija się jawne zapisanie odwołania do długości tablicy.
Znacznie bardziej interesującym aspektem indeksowania jest użycie dwukropka (:
). W zasadzie to zamienia on wówczas całą operację na “krojenie” (slice) tablicy, bo pozwala on na na wybór nie jednego elementu, a całego przedziału. Dokładniej mówiąc tab[i:j]
oznacza fragment tablicy wyznaczony półotwartym zakresem indeksów . Kawałek ten zawiera więc
tab[i]
, ale pomija tab[j]
; jest to analogiczne chociażby do iteratorów begin()
i end()
w kontenerach STL.
To właśnie slicing jest tą nietypową operacją, która dla niewprawnego oka wygląda cokolwiek zagadkowo. Jest tak zwłaszcza wtedy, gdy wykorzystuje ona możliwość pominięcia jednego z krańców przedziału, który to jest wówczas “dociągany” do odpowiedniego krańca całej listy.
Łącząc wszystkie te zawiłości możemy już rozszyfrować większość często występujących przypadków użycia indeksowania w Pythonie:
Dwa ostatnie przykłady pokazują też, że tego rodzaju operacje są bardzo przydatne podczas przetwarzania łańcuchów znaków, które to “przypadkiem” są również swego rodzaju tablicami.
Takie myki nie tylko w wysokopoziomowych jezykach skryptowych – jezyk D posiada podobne “featury” jezeli chodzi o indeksowanie tablic i stringow. Implementacja czegos takiego jest przeciez w miare prosta takze na najnizszym poziomie. To tylko tablice ;)
Dodałbym jeszcze jeden fajny feature – możliwość dodania drugiego dwukropka i trzeciego parametru po nim – pozwala to brać co n-ty element. Przykłady:
>>> tab = [1, 2, 3, 4, 5, 6]
>>> tab[::2]
[1, 3, 5]
>>> tab[2:-2:2]
[3]
>>> tab[1::2]
[2, 4, 6]
Jak widać jest to jeszcze bardziej niejasne dla kogoś kto nie wie o co chodzi ;)
MSM, w przypadku trójargumentowym, nie trzeci a drugi parametr wskazuje skok – skrajne argumenty wskazują początek i koniec.
>>> a = [1,2,3,4,5,6,7]
>>> a[0:-1:5]
[1, 6]
Zapis jest niejasny tylko dla ludzi, którzy nigdy wcześniej nic nie mieli z nim wspólnego. ;)
Ogólnie przydatna rzecz, podobna konstrukcja w Matlabie jest bardzo użyteczna, chociaż o nieco innym skutku ubocznym – nie jest związana bezpośrednio z operatorem wyłuskania elment(u|ów) z tablicy, a generuje tablicę wartości, która może, ale nie musi, posłużyć do dalszego indeksowania.
@Sebas86 Ale czy właśnie sam się nie negujesz? Wypisujesz tablicę od pierwszego do ostatniego elementu co 5 elementów, czyli właśnie [1,6] – trzeci parametr wskazuje na skok.
W Pythonie trzeci parametr wskazuje na skok, w Matlabie drugi. Między innymi dlatego, że jest to tak mylące, to o tym nie wspominałem ;)
Stringi (łańcuchy) można indeksować, jednak nie są one listami/tablicami – nie można ich modyfikować: