Wednesday, January 14, 2009

Python - classmethods czyli staticmethod na sterydach

Dziś natknąłem się na fajny przykład zastosowania pythonowego classmethod :) Najlepszy jaki widziałem w sieci. Wpis ten więc oprę o ten właśnie przykład.

Po pierwsze jakie cechy posiadają tak zdefiniowane metody:
* można wywołać je zarówno na klasie jak i na obiekcie
* zamiast parametru self (obiektu) przyjmuje parametr cls - klasę dla której została wywołana

Cechy dość ciekawe. Spójrzmy co z nich wynika. Po pierwsze - aby użyć metody zdefiniowanej jako classmethod nie musimy tworzyć obiektu :) możemy wywołać ją na klasie. Jednak ! względem metody statycznej, możemy dowiedzieć się na jakiej klasie ją wywołujemy ! Jest to szalona wygoda :) Na początku napiszmy klasę z metodą statyczną, która nadaje wartość jakiemuś statycznemu parametrowi.
class A(object):
    counter = 0

    @staticmethod
    def setCounter(value):
        A.counter = value

if __name__ == '__main__':
    A.setCounter(10)
Proste. Utworzyliśmy klasę z atrybutem counter oraz prostą metodę statyczną, która pozwala na ustawienie mu wartości. Zauważ pewną ciekawą rzecz. Aby odwołać się do zmiennej counter klasy A, po wejściu do metody statycznej musisz wpisać nazwę klasy "z ręki". Od wewnątrz metoda statyczna jest pozbawiona jakichkolwiek informacji o obiekcie, do którego przynależy i nie ma się jak go chwycić. Nie posiada ani parametru self aby odwołać się odbiektu - a przez niego do klasy, ani nic innego. Jedynym wyjściem aby odwołać się do klasy jest użycie jej nazwy explicite (wprost). Szalenie nieeleganckie rozwiązanie a przede wszystkim niewygodne. Dlaczego ? Gdy klasa zmieni swoją nazwę będzie trzeba zmieniać ją we wszystkich takich wystąpieniach. Ale to nie początek "problemów" zobacz co się stanie gdy odziedziczysz po klasie A.
class B(A):
    pass

if __name__ == '__main__':
    B.setCounter(20)
Gdy teraz wypiszesz A.counter okaże się, że wynosi on 20. Sytuacja jest jasna - klasa B odziedziczyła metodę jednak wciąż wywoływane jest A.counter = value i ustawiana jest wartość dla A. Może być to działanie zamierzone, jednak my chcielibyśmy aby klasy, które dziedziczą po naszej - posiadały swoje własny liczniki. Wystarczy tylko odrobinkę zmienić klasę A i po kłopocie. Retusz będzie dotyczył naszej metody, która powinna wyglądać tak:
@classmethod
def setCounter(cls, value):
cls.counter = value

I to wszystko ? Tak to wszystko :) Przyjrzyjmy się zmianom. Po pierwsze teraz nasza metoda nie jest statyczna - lecz jest metodą oznaczoną jako classmethod. Taką metodę możemy nadal wywoływać na klasie lub na obiekcie. Metody klasy jako pierwszy parametr przyjmują klasę dla której są wywoływane. Tutaj użyliśmy zmiennej o nazwie cls. Dzięki temu nasza metoda stała się uniwersalna. wywołanie metody setCounter dla klasy A będzie wyglądało (poglądowo) tak:
setCounter(A, 10):
    A.counter = 10
a dla B tak:
setCounter(B, 20):
    B.counter = 20
O taki dokładnie efekt nam chodziło :) brawo ! Czas na podsumowanie.

Metody klasy możemy traktować jak pewnego rodzaju dodatek do metod statycznych, który zmienia ich zachowanie. Po pierwsze metody klasy posiadają więcej informacji. Wiedzą dla której klasy są wywoływane co pozwala nam wykorzystać tą cenną informację :) Po drugie nie zmuszają nas do wpisywania explicite nazwy klasy (bo mamy ją w zmiennej) co oszczędza mnóstwo czasu na refaktoryzacji kodu.

http://docs.python.org/library/functions.html#classmethod

No comments: