Showing posts with label klasy. Show all posts
Showing posts with label klasy. Show all posts

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

Python - sloty

Najprostrzą metodą zdefiniowania w której dostępne będą wyłącznie zdefiniowane przez nas pola jest użycie slotów.

class Klasa(object):
    __slots__ = ['x', 'y']

if __name__ = '__name__':
    k = Klasa()
    k.x = 1
    k.y = 2
    k.a = 3
Traceback (most recent call last):
File "py.py", line 8, in ?
k.a = 3
AttributeError: 'Klasa' object has no attribute 'a'

Jak widać możemy przypisać wartości wyłącznie parametrom zdefiniowanym w __slots__.

http://docs.python.org/reference/datamodel.html#__slots__

Saturday, January 10, 2009

Python - kontrola dostępu do parametrów obiektu i klasy

O kontroli dostępu do składowych klasy pisałem już troszeczkę we wpisie Python - hermetyzacja i enkapsulacja z punktu widzenia programisty C++. Jest to jedna z metod kontroli dostępu jednak na małą skalę. Nadaje się świetnie do sprawowania pieczy nad dostępem do kilku pojedynczych atrybutów. W Pythonie istnieją jednak mechanizmy pozwalające na globalną kontrolę tego procesu. Zobaczmy jak to działa.

Python proponuje tutaj dwa podstawowe rodzaje metod. Pierwszą z nich, którą omówimy, będzie __setattr__(self, name, value). Metoda ta wywoływana jest za każdym razem gdy staramy się przypisać jakiemuś parametrowi klasy wartość. Żeby sprawdzić jak to działa spróbujmy następującego przykładu:
class Klasa(object):
    def __setattr__(self, name, value):
        print 'Ustawiasz atrybut ' + str(name) + ' na ' + str(value)

if __name__ == '__main__':
    k = Klasa()
    k.a = 1
    k.imie = 'Jan'
    k.ja = 'ty'
    k.liczba = 123.4
Za każdym razem gdy ustawiamy jakiś atrybut wywoływana jest nasza funkcja __setattr__ :) Fantastyczne narzędzie :) Problem polega jednak na tym, że w momencie, w którym nadpisaliśmy tą funkcję musimy sami zadbać o przypisanie odpowiednich wartości. Kiedy poznawałem opisywany mechanizm próbowałem ponownie wywołać funkcję __setattr__ dla klasy albo przypisać go wewnątrz tradycyjną metodą poprzez znak równości. Kończyło się to rekurencyjnym zapętleniem się wywołań metody __setattr__ a to z kolei błędem.

Metodą proponowaną w dokumentacjidokumentacji, dla new-style classes, a tylko o takich piszę, jest wywołanie tej metody na obiekcie bazowym. Tak więc aby nasza klasa rzeczywiście zapamiętywała przypisywane atrybutom wartości nasza metoda __setattr__ powinna wyglądać tak:
class Klasa(object):
    def __setattr__(self, name, value):
        print 'Ustawiasz atrybut ' + str(name) + ' na ' + str(value)
        object.__setattr__(self, name, value)
i wszystko będzie działać jak powinno. Co nam daje ta metoda ? Pozwala nam tak naprawdę na kontrolę tego co i w jaki sposób jest przypisywane do naszej klasy nawet w przypadku, w gdy przypisywany jest parametr, którego nie zdefiniowaliśmy (sic!). Moglibyśmy dzięki tej metodzie sprawdzać czy w naszej klasie istnieje atrybut, któremu wartość chce nadać programista i wyrzucić wyjątek gdy taki nie jest zdefiniowany. Bardzo elastyczne narzędzie.

Czas na pobieranie atrybutów. Początkowo w języku Python istniała metoda __getattr__(self, name). Była wywoływana za każdym razem gdy odwoływaliśmy się do nie istniejącego w klasie atrybutu i chcieliśmy jakoś na to zareagować, np. zwrócić wyjątek lub coś innego. Pokaże na przykładzie jak to wygląda.
class Klasa(object):
    a = None
    def __getattr__(self, name):
        print 'Atrybutu ' + name + ' nie ma w naszej klasie.'

if __name__ == '__main__':
    k = Klasa()
    k.a
    k.b
Program wpisze komunikat "Atrybutu b nie ma w naszej klasie.". I bardzo dobrze. Kiedy odwołaliśmy się do atrybutu, który nie istniej Python skorzystał z metody __getattr__(self, name). Metoda nie została wywołana przy atrybucie a gdyż został wcześniej zdefiniowany.

Gdybyśmy chcieli zabezpieczyć naszą klasę przed przypisywaniem wartości nasza metoda __getattr__ powinna zgłaszać wyjątek raise AttributeError :) W ten sposób próba dopisania do obiektu czegoś czego nie zdefiniowaliśmy kończyła by się błędem (wyjątkiem).

Tak było w Pythonie przed wersją 2.2. W wraz z wejściem new-style classes postanowiono wprowadzić metodę __getattribute__(self, name), która w przeciwieństwie do __getattr__ zadziała zawsze (zarówno gdy atrybut, który chcemy pobrać istnieje jak również gdy nie istnieje). Zapożyczę przykład fofoo z jednego z wątków z forum PPCG w którym pytałem o dokładniejsze wyjaśnienie tego mechanizmu.
class C(object):
    x = None
    def __getattr__(self, name):
        print '__getattr__', name
        #raise AttributeError
    def __getattribute__(self, name):
        print '__getattribute__',name
        if name == 'whoops':
            raise AttributeError
        return object.__getattribute__(self, name)
    def metoda(self): pass

if __name__ == '__main__':
    c = C()
    c.x
    print
    c.metoda()
    print
    c.a
    print
    c.whoops
Metoda __getattribute__ wywoła się za każdym razem :) Ponieważ czasami atrybut istnieje i chcemy uzyskać do niego dostęp musimy na końcu zwrócić object.__getattribute__(self, name). W tym momencie Python da nam dostęp do elementu, który wskazaliśmy. Jeżeli jednak nie istnieje sterowanie zostanie przekazane do metody __getattr__. Metoda __getattr__ zostanie również wywołana, jeżeli w __getattribute__ zgłosimy wyjątek AttributeError :)

Aby dopełnić informacje na koniec należałoby dodać iż z metod takich jak __getattribute__ czy __getattr__ oraz __setattr__ do parametrów można dobierać się w bezpieczny sposób (nie powodujący rekurencyjnego wywołania tych funkcji) również poprzez słowniki. Dla przykładu.
class Klasa(object):
    a = 'a1'
    def __init__(self):
        self.b = 'b1'

if __name__ == '__main__':
    k = Klasa()
    print k.__dict__['b']
    print type(k).__dict__['a']
Nasza klasa posiada parametr klasowy a i parametr instancji b. Oba są trzymane w słownikach. Aby dobrać się do słownika konkretnego obiektu wystarczy użyć k.__dict__, dobranie się do słownika samej klasy wymaga użycia type(k).__dict__ :)

Informacje w dokumentacji dotyczące tematu oraz jego rozwinięcie znajdziecie pod adresami:
http://docs.python.org/reference/datamodel.html#customizing-attribute-access

Wednesday, January 7, 2009

Python - pierwszy parametr metody to obiekt i co z tego wynika

Kiedy definiujemy jakąś metodę, nie statyczną, w języku Python jako pierwszy parametr każdej metody podajemy obiekt klasy (zwyczajowo nazywamy tę zmienną self).

No tak. Działa to jak "this" z C++ no i pojawia się to dziwne self w deklaracji metody jednak - co z tego dla nas wynika ? Otóż pewna piękna rzecz :) Możemy wywołać metodę na klasie i podać obiekt, na której ma się wywołać. Wygląda to na przykład tak.
class Samochod(object):
    def dzwiek(self):
        print 'brum'

if __name__ == '__main__':
    s = Samochod()
    Samochod.dzwiek(s)
Jak widać stworzyliśmy obiekt i przekazaliśmy go jako pierwszy parametr do metody wywoływanej na klasie :) Ktoś powie "dziwne". I rzeczywiście dziwne. Sprawa zaczyna nabierać jednak nabierać sensu rumieńców w kilku przypadkach. Jednym z nich jest sytuacja gdy nasza klasa dziedziczy z wielu klas i chociaż dwie z nich mają metodę o tej samej nazwie. Wyobraź sobie jak mogłaby wyglądać amfibia: jedź jak samochód i pływa jak łódź. Umówmy się, że samochód wydaje dźwięk brum a łódź plum. Spróbujemy to jakoś ubrać w klasy. Oto moja propozycja.
class Samochod(object):
    def dzwiek(self):
        print 'brum'

class Lodz(object);
    def dzwiek(self):
        print 'plum'

class Amfibia(Samochod, Lodz):
    pass
Jak widać klasa Amfibia dziedziczy zarówno po Samochod jak i po Lodz :) i pięknie. Jak myślisz ? jaki dźwięk wyda amfibia gdy wywołamy na niej tą metodę ?

a = Amfibia()
a.dzwiek()
brum

Ale dlaczego akurat brum ? Odpowiedź jest bardzo prosta. Amfibia w kolejności dziedziczy najpierw po samochodzie później po łodzi. Zostanie wzięta funkcja klasy pierwszej od lewej. Możesz to łatwo sprawdzić definiując klasę Amfibia jako class Amfibia(Lodz, Samochod) :) teraz dźwięk będzie dźwiękiem łodzi.

Pozostańmy przy amfibii, która najpierw dziedziczy po samochodzie class Amfibia(Lodz, Samochod). O ile wydanie dźwięku samochodu jak widać nie sprawia problemu o tyle, nie za bardzo jest możliwość, w tradycyjny sposób, dostać się do dźwięku łodzi. Otóż gdy wywołasz z amfibii dźwięk "potomka" wywoła się dźwięk samochodu, gdyby zaś w procedurze samochodu został wywołany dźwięk potomka to wywołałby się dźwięk łodzi. Coś w tym stylu.
class Samochod(object):
    def dzwiek(self):
        print 'brum'
        super(Samochod, self).dzwiek()

class Lodz(object);
    def dzwiek(self):
        print 'plum'

class Amfibia(Samochod, Lodz):
    def dzwiek(self):
        super(Amfibia, self).dzwiek()

if __name__ == '__main__':
    a = Amfibia()
        a.dzwiek()
Metoda dźwięk z Amfibii odwołuje się do metody dźwięk pierwszego (od lewej) potomka - czyli u nas samochodu. Wywoływana jest metoda dźwięk z samochodu.
W metodzie samochodu znowu pobierany jest potomek. Samochód sam w sobie nie ma potomka, ale wywołujemy go "z amfibii" jest więc pobierany kolejny potomek (tutaj łódź) i wywoływany jest dla niego metoda dźwięk().

Z naszego punktu widzenia wielodziedziczenie działa tak: amfibia (klasa) dziedziczy po samochodzie i po łodzi. Już wiemy, że potomkiem amfibii jest pierwsza klasa od lewej wymieniona w dziedziczeniu (u nas klasa Samochod), a kolejnym potomkiem (potomkiem szukanym w metodzie klasy samochód) jest łódź. "Czyli co ?" ktoś zapyta "Amfibia dziedziczy po samochodzie, który dziedziczy po łodzi ?". Tak to właśnie wygląda od strony wywoływania metod. Aby dostać się do dźwięku łodzi trzeba go wywołać w samochodzie.

Jak dobrze pomyśleć to wydaje się to straszne. Mając taki kod kiedy teraz zrobimy:
s = Samochod()
s.dzwiek()
To wyda się dźwięk samochodu i będzie błąd ponieważ metoda super próbuje znaleźć potomka samochodu (jedynie object) i wydać na nim dźwięk - co jest niemożliwe. Potomek samochodu ma sens tylko w przypadku użycia amfibii. Tak więc klops. Klasa samochód sama w sobie, tak skonstruowana, będzie powodowała błędy.

Jeżeli byliście uważnymi czytelnikami użyłem na początku sformułowania "w tradycyjny sposób". To znaczy, że Python pomimo takiej swojej śmiesznej metody wielodziedziczenia daje mechanizmy aby zrobić klasę samochód i łódź takie aby działały samodzielnie, oraz amfibię w której będzie można wydać oba dźwięki. Cała sztuczka polega właśnie na tym :) że jako pierwszy parametr metody podajemy obiekt, na którym ma się wywołać.

Zdefiniujmy na początku samochód i łódź - najnormalniej na świecie:
class Samochod(object):
    def dzwiek(self):
        print 'brum'

class Lodz(object);
    def dzwiek(self):
        print 'plum'
Bardzo proste. Obydwie mogą wydać dźwięk i widzimy, że nie odwołują się do żadnych metod "potomków" więc mogą działać sobie naprawdę niezależnie. Dobrze. Troszkę zmian będzie w amfibii. Teraz nasza amfibia będzie wydawała dwa dźwięki jednocześnie. Jak to zrobimy. Otóż - Wywołamy metodę dźwiek na klasie (nie na obiekcie) Samochod i klasie (nie na obiekcie) Lodz podając jako obiekt, na którym należy wywołać metodę, amfibię. Wszystko dzięki temu iż składnia Python wymusza na nas aby pierwszy parametr każdej metody, nie statycznej, był obiekt na którym ma działać nasza metoda. Wszystko co opisaliśmy powyżej wygląda dokładnie tak:
class Amfibia(Samochod, Lodz):
    def dzwiek(self):
        Samochod.dzwiek(self)
        Lodz.dzwiek(self)

if __name__ == '__main__':
    a = Amfibia()
    a.dzwiek()
Co się tu dzieje ? Ponieważ już wiemy, że metodą super z amfibii dostaniemy się do pierwszej (od lewej) klasy po której dziedziczy nasza, użyliśmy innego zabiegu. Wywołaliśmy metodę dźwięk na klasie Samochod. Jednak ponieważ metoda ta nie jest statyczna i trzeba ją wywołać na obiekcie jako pierwszy parametr podaliśmy obiekt, na którym operacja ma się wykonać - self czy w tym przypadku naszą amfibię. To samo zrobiliśmy dla łodzi. Ponieważ amfibia dziedziczy i po samochodzie i po łodzi będzie dobrze zachowywała się zarówno z metodami pierwszej jak i drugiej klasy.

Taka składnia wywołania to jeden z wielu smaczków Python-a wynikającej z troistości jego natury: strukturalne, obiektowej i tej którą widać tutaj - funkcjonalnej :] Nie oceniam czy to jak rozwiązane jest w Pythonie sprawdza się czy nie - czas pokaże.

Uwaga !
Użycie metody print wewnątrz klas jest zabiegiem upraszczającym zagadnienie mającym na celu ułatwienie zrozumienia problemu i kodu w celu zwiększenia jego walorów dydaktycznych. Autor świadomie pogwałcił zasady dotyczących I/O klasy i obiektów w celu IMHO uczynienia przykładu czytelniejszym i przejrzystszym.

Monday, December 22, 2008

Python - klasy z punktu widzenia programisty C++

Kiedy tworzyliśmy klasę w C lub C++ używaliśmy słowa kluczowego class lub struct (od tego zależał domyślny tryb widoczności dla elementów klasy). Trzeba było zapisać co jest private, co protected a co public. Czasami potrzebowaliśmy aby jakaś metoda lub atrybut klasy był statyczny. Do wszystkiego używaliśmy specjalnych słów kluczowych. Jeżeli chcieliśmy stworzyć konstruktor tworzyliśmy metodę o nazwie naszej klasy a destruktor - podobnie, tyle tylko, że nazwę tą poprzedzaliśmy tyldą.

Wszystko omówię na podstawie przykładowej klasy Pythona.

class MojaKlasa(object):
    zmienna_statyczna = 12

    def __init__(self):
        self.zwykla_zmienna = 'a'
        print 'Ja się wywołam przy toworzeniu'

    def __del__(self):
        print 'Ja się wywołam przy usuwaniu'

Powyższa klasa dziedziczy po object. Dalej w ciele klasy definiujemy zmienną o nazwie zmienna_statyczna i przypisujemy jej wartość 12. W języku Python wszystkie zmienne definiowane w ciele klasy zachowują się jak zmienne statyczne. Metoda __init__ (nazwa zaczyna się i kończy od dwóch podkreślników) wywoła się zaraz po utworzenia obiektu :) Zaś metoda __del__  podczas jego usuwania. Piszę celowo "wykona się zaraz po utworzeniu", a nie "jest konstruktorem" ponieważ jest to stwierdzenie bliższe prawdzie.

Metody statyczne oznaczamy podając przed ich deklaracją @staticmethod:

class Klasa(object):
    zmienna = 12

    @staticmethod
    def mojaStatycznaMetoda():
        return Klasa.zmienna
Metoda jak widać nie przyjmuje parametru self (nie miałoby to sensu skoro jest statyczna i może być wywołana dla klasy bez tworzenia jej instancji). Aby odwołać się w niej do zmiennej statycznej możemy użyć Klasa.zmienna gdzie klasa to nazwa klasy a zmienna to nazwa zmiennej zadeklarowanej w jej ciele. Podobnie wygląda wywołanie metody statycznej Klasa.mojaStatycznaMetoda().

Wszystkie metody przyjmują jako pierwszy parametr self, czyli "samego siebie". Jest to jawny zapis tego co w C++ jest ukryte w mechanizmach języka i traktowane "domyślnie" (wszak możesz w metodach klasy C++ odwoływać się do obiektu z użyciem słówka this chociaż nigdzie go nie przekazujesz). Wszystkie atrybuty zdefiniowane w metodzie __init__ z użuyciem self.nazwa_zmiennej są jak widać zmienny obiektu nie klasy co po przeanalizowaniu znaczenia zmiennej self (ja, moja instancja, aktualnie tworzony obiekt) ma rzeczywiście sens :) Zmienne należą do obiektu (self), są definiowane dla niego (konkretnej instancji) nie zaś w ciele samej klasy.

A co z atrybutami: prywatny, publiczny. Domyślnie wszystkie metody i atrybuty w Pythonie są publiczne. I tutaj, aby zdefiniować elementy prywatne, zrezygnowano ze słów kluczowych na rzecz konwencji. Aby element klasy był prywatny jego nazwa musi zaczynać się od dwóch podkreślników to jest od __.  Tak więc atrybuty:


class KolejnkaKlasa(object):
    __zmienna_statyczna
    def __init__(self):
        self.__top_secret

    def __nie_wywolasz_mnie(self):
        pass
    pass
Wszystko czego nazwa zaczyna się od dwóch podkreślników będzie prywatne, ze swojej natury.
Co można powiedzieć o wartościach protected ? Są - ale istnieją jedynie z umowy. Umownie - wszystko czego nazwa rozpoczyna się od jednego znaku podkreślenia jest parametrem chronionym. Umowanie - ponieważ faktycznie ma się do niego nadal publiczny dostęp. Czy to znaczy, że Python nie wspiera metod i atrybutów chronionych w klasach. Tak - w Pythonie nie zaimplementowano tego mechanizmu. Zrobiono to celowo. Zainteresowanych odsyłam do wątku "Protected Methods and Python" na grupie dyskusyjnej języka. Jak widać po dyskusji, ufając intuicji autorytetu jakim jest Bjarne StrouStrup (twórca języka C++) atrybty i metody chronione to zły, zbędny mechanizm :) Więc nie martw się nic nie tracisz :)