Indeksowanie w Pythonie

2011-05-15 16:34

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:

  1. int tab[N];
  2. // ...
  3. assert( tab[i] == *(tab + i) );

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 [i; j). 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:

  1. tab[1:] # tablica bez pierwszego elementu
  2. tab[:-1] # tablica bez ostatniego elementu
  3. tab[:n] # co najwyżej n początkowych elementów tablicy
  4. tab[-n:] # n > 0 końcowych elementów tablicy
  5.  
  6. # początkowy ciąg aż do wystąpienia znaku @
  7. username = email[:email.index('@')]
  8.  
  9. # końcowy ciąg począwszy od ostatniej kropki
  10. extension = filename[filename.rindex('.'):]

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.

Be Sociable, Share!
Be Sociable, Share!
Tags: , , ,
Author: Xion, posted under Programming »


6 comments for post “Indeksowanie w Pythonie”.
  1. vashpan:
    May 15th, 2011 o 17:26

    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 ;)

  2. MSM:
    May 15th, 2011 o 18:56

    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 ;)

  3. Sebas86:
    May 15th, 2011 o 21:37

    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.

  4. p:
    May 15th, 2011 o 21:49

    @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.

  5. Xion:
    May 16th, 2011 o 19:04

    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 ;)

  6. gryf:
    May 17th, 2011 o 21:12

    Stringi (łańcuchy) można indeksować, jednak nie są one listami/tablicami – nie można ich modyfikować:

    1. >>> a = [1,2,3,4,5]
    2. >>> b = "razdwatrzy"
    3. >>> del(a[:-2])
    4. >>> a
    5. [4, 5]
    6. >>> del(b[:-2])
    7. Traceback (most recent call last):
    8.   File "", line 1, in
    9. TypeError: 'str' object does not support item deletion
    10. >>> b[3] = "f"
    11. Traceback (most recent call last):
    12.   File "", line 1, in
    13. TypeError: 'str' object does not support item assignment
Comments are disabled.
 


© 2020 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.