Zanurkuj w Pythonie/Metody specjalne

Pobieranie i ustawianie elementów

edytuj

Oprócz normalnych metod, jest też kilka (może kilkanaście) metod specjalnych, które można definiować w klasach Pythona. Nie wywołujemy ich bezpośrednio z naszego kodu (jak zwykłe metody). Wywołuje je za nas Python w określonych okolicznościach lub gdy użyjemy określonej składni np. za pomocą metod specjalnych możemy nadpisać operację dodawania, czy też odejmowania.

Z normalnym słownikiem możemy zrobić dużo więcej, niż bezpośrednio wywołać jego metody. Same metody nie wystarczą. Możemy na przykład pobierać i wstawiać elementy dzięki wykorzystaniu odpowiedniej składni, bez jawnego wywoływania metod. Możemy tak robić dzięki metodom specjalnym. Python odpowiednie elementy składni przekształca na odpowiednie wywołania funkcji specjalnych.

Przykład. Metoda __getitem__
>>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3")
>>> f
{'plik':'/music/_singles/kairo.mp3'}
>>> f.__getitem__("plik")               #(1)
'/music/_singles/kairo.mp3'
>>> f["plik"]                           #(2)
'/music/_singles/kairo.mp3'
  1. Metoda specjalna __getitem__ wygląda dość prosto. Ta metoda specjalna pozwala słownikowi zwrócić pewną wartość na podstawie podanego klucza. A jak tę metodę możemy wywołać? Możemy to zrobić bezpośrednio, ale w praktyce nie robimy w ten sposób, byłoby to niezbyt wygodne. Najlepiej pozwolić Pythonowi wywołać tę metodę za nas.
  2. Z takiej składni korzystamy, by dostać pewną wartość ze słownika. W rzeczywistości Python automatycznie przekształca taką składnię na wywołanie metody f.__getitem__("plik"). Właśnie dlatego __getitem__ nazywamy metodą specjalną: nie tylko możemy ją wywołać, ale Python wywołuje tę metodę także za nas, kiedy skorzystamy z odpowiedniej składni.

Istnieje także analogiczna do __getitem__ metoda __setitem__, która zamiast pobierać pewną wartość, zmienia daną wartość korzystając z pewnego klucza.

Przykład. Metoda __setitem__
>>> f
 {'plik':'/music/_singles/kairo.mp3'}
 >>> f.__setitem__("gatunek", 31)         #(1)
 >>> f
 {'plik':'/music/_singles/kairo.mp3', 'gatunek':31}
 >>> f["gatunek"] = 32                    #(2)
 >>> f
 {'plik':'/music/_singles/kairo.mp3', 'gatunek':32}
  1. Analogicznie do __getitem__, możemy za pomocą __setitem__ zmienić wartość pewnego klucza znajdującego się w słowniku. Podobnie, jak w przypadku __getitem__ nie musimy jej wywoływać w sposób bezpośredni. Python wywoła __setitem__, jeśli tylko użyjemy odpowiedniej składni.
  2. W taki praktyczny sposób korzystamy ze słownika. Za pomocą tej linii kodu Python wywołuje w sposób ukryty f.__setitem__("gatunek", 32).

__setitem__ jest metodą specjalną, ponieważ Python wywołuje ją za nas, ale ciągle jest metodą klasy. Kiedy definiujemy klasy, możemy definiować pewne metody, nawet jeśli nadklasa ma już zdefiniowaną tę metodę. W ten sposób nadpisujemy (ang. override) metody nadklas. Tyczy się to także metod specjalnych.

Koncepcja ta jest bazą całego szkieletu, który analizujemy w tym rozdziale. Każdy typ plików może posiadać własną klasę obsługi, która wie, w jaki sposób pobrać metadane z konkretnego typu plików. Natychmiast po poznaniu niektórych atrybutów (jak nazwa pliku i położenie), klasa obsługi będzie wiedziała, jak pobrać dalsze metaatrybuty automatycznie. Możemy to zrobić poprzez nadpisanie metody __setitem__, w której sprawdzamy poszczególne klucze i jeśli dany klucz zostanie znaleziony, wykonujemy dodatkowe operacje.

Na przykład MP3FileInfo jest podklasą FileInfo. Kiedy w MP3FileInfo ustawiamy klucz "plik", nie tylko ustawiamy wartość samego klucza "plik" (jak to robi słownik), lecz także zaglądamy do samego pliku, odczytujemy tagi MP3 i tworzymy pełny zbiór kluczy. Poniższy przykład pokazuje, w jaki sposób to działa.

Przykład. Nadpisywanie metody __setitem__ w klasie MP3FileInfo
    def __setitem__(self, key, item):         #(1)
        if key == "plik" and item:            #(2)
            self.__parse(item)                #(3)
        FileInfo.__setitem__(self, key, item) #(4)
  1. Zwróćmy uwagę na kolejność i liczbę argumentów w __setitem__. Pierwszym argumentem jest instancja danej klasy (argument self), z której ta metoda została wywołana, następnym argumentem jest klucz (argument key), który chcemy ustawić, a trzecim jest wartość (argument item), którą chcemy skojarzyć z danym kluczem. Kolejność ta jest ważna, ponieważ Python będzie wywoływał tę metodą w takiej kolejności i z taką liczbą argumentów. (Nazwy argumentów nic nie znaczą, ważna jest ich ilość i kolejność.)
  2. W tym miejscu zawarte jest sedno całej klasy MP3FileInfo: jeśli przypisujemy pewną wartość do klucza "plik", chcemy wykonać dodatkowo pewne operacje.
  3. Dodatkowe operacje dla klucza "plik" zawarte są w metodzie __parse. Jest to inna metoda klasy MP3FileInfo. Kiedy wywołujemy metodę __parse używamy zmiennej self. Gdybyśmy wywołali samo __parse, odnieślibyśmy się do normalnej funkcji, która jest zdefiniowana poza klasą, a tego nie chcemy wykonać. Kiedy natomiast wywołamy self.__parse będziemy odnosić się do metody znajdującej się wewnątrz klasy. Nie jest to niczym nowym. W identyczny sposób odnosimy się do atrybutów obiektu.
  4. Po wykonaniu tej dodatkowej operacji, chcemy wykonać metodę nadklasy. Pamiętajmy, że Python nigdy nie zrobi tego za nas; musimy zrobić to ręcznie. Zwróćmy uwagę na to, że odwołujemy się do bezpośredniej nadklasy, czyli do FileInfo, chociaż on nie posiada żadnej metody o nazwie __setitem__. Jednak wszystko jest w porządku, ponieważ Python będzie wędrował po drzewie przodków jeszcze wyżej dopóki nie znajdzie klasy, która posiada metodę, którą wywołujemy. Tak więc ta linia kodu znajdzie i wywoła metodę __setitem__, która jest zdefiniowana w samej wbudowanej klasie słownika, w klasie dict.
Przykład. Ustawianie klucza "plik" w MP3FileInfo
>>> import fileinfo
 >>> mp3file = fileinfo.MP3FileInfo()                   #(1)
 >>> mp3file
 {'plik':None}
 >>> mp3file["plik"] = "/music/_singles/kairo.mp3"      #(2)
 >>> mp3file
 {'album': 'Rave Mix', 'rok': '2000', 'komentarz': 'http://mp3.com/DJMARYJANE',
u'tytu\u0142': 'KAIRO****THE BEST GOA', 'artysta': '***DJ MARY-JANE***', 
'gatunek': 31, 'plik': '/music/_singles/kairo.mp3'}
 >>> mp3file["plik"] = "/music/_singles/sidewinder.mp3" #(3)
 >>> mp3file
 {'album': '', 'rok': '2000', 'plik': '/music/_singles/sidewinder.mp3',
'komentarz': 'http://mp3.com/cynicproject', u'tytu\u0142': 'Sidewinder',
'artysta': 'The Cynic Project', 'gatunek': 18}
  1. Najpierw tworzymy instancję klasy MP3FileInfo bez podawania nazwy pliku. (Możemy tak zrobić, ponieważ argument filename metody __init__ jest opcjonalny.) Ponieważ MP3FileInfo nie posiada własnej metody __init__, Python idzie wyżej po drzewie nadklas i znajduje metodę __init__ w klasie FileInfo. Z kolei __init__ w tej klasie ręcznie wykonuje metodę __init__ w klasie dict, a potem ustawia klucz "plik" na wartość w zmiennej filename, który wynosi None, ponieważ pominęliśmy nazwę pliku. Ostatecznie mp3file początkowo jest słownikiem (a właściwie instancją klasy potomnej słownika) z jednym kluczem "plik", którego wartość wynosi None.
  2. Teraz rozpoczyna się prawdziwa zabawa. Ustawiając klucz "plik" w mp3file spowoduje wywołanie metody __setitem__ klasy MP3FileInfo (a nie słownika, czyli klasy dict). Z kolei metoda ta zauważa, że ustawiamy klucz "plik" z prawdziwą wartością (item jest prawdą w kontekście logicznym) i wywołuje self.__parse. Chociaż jeszcze nie analizowaliśmy działania metody __parse, możemy na podstawie wyjścia zobaczyć, że ustawia ona kilka innych kluczy jak "album", "artysta", "gatunek", u"tytuł" (w unikodzie, bo korzystamy z polskich znaków), "rok", czy też "komentarz".
  3. Kiedy zmienimy klucz "plik", proces ten zostanie wykonany ponownie. Python wywoła __setitem__, który następnie wywoła self.__parse, a ten ustawi wszystkie inne klucze.