Zanurkuj w Pythonie/Metody specjalne
Pobieranie i ustawianie elementów
edytujOpró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.
__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'
- 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. - 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.
__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}
- 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. - 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.
__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)
- Zwróćmy uwagę na kolejność i liczbę argumentów w
__setitem__
. Pierwszym argumentem jest instancja danej klasy (argumentself
), z której ta metoda została wywołana, następnym argumentem jest klucz (argumentkey
), który chcemy ustawić, a trzecim jest wartość (argumentitem
), 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ść.) - W tym miejscu zawarte jest sedno całej klasy MP3FileInfo: jeśli przypisujemy pewną wartość do klucza "plik", chcemy wykonać dodatkowo pewne operacje.
- Dodatkowe operacje dla klucza "plik" zawarte są w metodzie
__parse
. Jest to inna metoda klasyMP3FileInfo
. Kiedy wywołujemy metodę__parse
używamy zmiennejself
. 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łamyself.__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. - 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 klasiedict
.
Kiedy odwołujemy się do danych zawartych w atrybucie instancji, musimy określić nazwę atrybutu np. self.attribute . Podczas wywoływania metody klasy, musimy określić nazwę metody np. self.method .
|
"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}
- Najpierw tworzymy instancję klasy
MP3FileInfo
bez podawania nazwy pliku. (Możemy tak zrobić, ponieważ argumentfilename
metody__init__
jest opcjonalny.) PonieważMP3FileInfo
nie posiada własnej metody__init__
, Python idzie wyżej po drzewie nadklas i znajduje metodę__init__
w klasieFileInfo
. Z kolei__init__
w tej klasie ręcznie wykonuje metodę__init__
w klasiedict
, a potem ustawia klucz"plik"
na wartość w zmiennejfilename
, który wynosiNone
, ponieważ pominęliśmy nazwę pliku. Ostateczniemp3file
początkowo jest słownikiem (a właściwie instancją klasy potomnej słownika) z jednym kluczem"plik"
, którego wartość wynosiNone
. - Teraz rozpoczyna się prawdziwa zabawa. Ustawiając klucz
"plik"
wmp3file
spowoduje wywołanie metody__setitem__
klasyMP3FileInfo
(a nie słownika, czyli klasydict
). Z kolei metoda ta zauważa, że ustawiamy klucz"plik"
z prawdziwą wartością (item
jest prawdą w kontekście logicznym) i wywołujeself.__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"
. - Kiedy zmienimy klucz
"plik"
, proces ten zostanie wykonany ponownie. Python wywoła__setitem__
, który następnie wywołaself.__parse
, a ten ustawi wszystkie inne klucze.