Zapisz jako PDF
Transkrypt
Zapisz jako PDF
Spis treści 1 Dekoratory 1.1 Zadanie 1 1.2 Zadanie 2 1.3 Zadanie 3 1.4 Zadanie 4 Dekoratory Dekoratory w Pythonie służą do zastępowania zdefiniowanych przez nas funkcji przez funkcje (lub inne obiekty) zgodnie z definicją dekoratora. Na początku najlepiej zapoznać się z prostym przykładem dekoratora, który powoduje, że przed i po wywołaniu dekorowanej funkcji wypisywane są linie informujące o tym jaka funkcja jest wywoływana: class decorator(object): def __init__(self, f): self.f = f def __call__(self): print 'przed wejściem do ', self.f.__name__ self.f() print 'po wyjściu z ', self.f.__name__ @decorator def g(): print 'wewnątrz g' g() Taki sam efekt można osiągnąć wpisując w ciało funkcji g dwie dodatkowe linijki, więc jakie są zalety wykorzystywania dekoratorów? Po pierwsze jeśli w podobny sposób chcemy zmodyfikować nie jedną ale wiele funkcji to napisanie dekoratora znacznie ułatwi to zadanie, ponadto dekorowanie funkcji jest łatwo zauważalne przez czytającego kod (dzięki @), pozwala na logiczne oddzielenie funkcjonalności na przykład właściwego działania funkcji od kodu realizującego logowanie użytkownika. Dekorowanie należy rozumieć jako składanie funkcji, kod z przykładu jest równoważny poniższemu: class decorator(object): def __init__(self, f): self.f = f def __call__(self): print 'przed wejściem do ', self.f.__name__ self.f() print 'po wyjściu z ', self.f.__name__ #@decorator def g(): print 'wewnątrz g' g = decorator(g) g() Dekoratory można definiować za pomocą klas lub funkcji, można dekorować funkcje mające argumenty lub nie, w końcu same dekoratory mogą przyjmować argumenty lub nie - omówimy teraz wszystkie te przypadki. Dekorator bezargumentowy zadany przez klasę musi mieć konstruktor przyjmujący jeden argument - funkcję, którą dekorujemy i implementować metodę __call__. Jeśli dekorujemy funkcję, która przyjmuje argumenty i chcemy aby funkcja po udekorowaniu przyjmowała takie same argumenty to metoda __call__ musi przyjmować takie argumenty: class decorator(object): def __init__(self, f): self.f = f def __call__(self, a, b, c): print 'przed wejściem do ', self.f.__name__ self.f(a, b, c) print 'po wyjściu z ', self.f.__name__ @decorator def g(a, b, c): print 'wewnątrz g: a =', a, 'b =', b, 'c =', c g(1, 2, 3) W tym przykładzie w czasie dekorowania zostanie wywołany konstruktor klasy decorator, a później przy wywołaniu funkcji g zostanie wywołana metoda __call__ klasy dekorator. Analogiczny dekorator zdefiniowany za pomocą funkcji musi być funkcją przyjmującą jeden parametr - funkcję, którą należy udekorować i zwracającą funkcję, którą funkcja dekorowana będzie zastąpiona: def decorator(f): def inner(a, b, c): print 'przed wejściem do ', f.__name__ f(a, b, c) print 'po wyjściu z ', f.__name__ return inner @decorator def g(a, b, c): print 'wewnątrz g: a =', a, 'b =', b, 'c =', c g(1, 2, 3) Tym razem przy dekorowaniu zostanie wykonana funkcja decorator, a przy wywoływaniu funkcji g będzie wywoływana funkcja inner Kolejnym zagadnieniem jest tworzenie dekoratorów przyjmujących argumenty. Dekorator przyjmujący argumenty będący klasą musi mieć konstruktor przyjmujący dane argumenty i metodę __call__ przyjmującą dokładnie jeden argument - funkcję dekorowaną - i zwracającą funkcję, którą należy podstawić w miejsce dekorowanej: class decorator(object): def __init__(self, a): self.a = a def __call__(self, f): def inner(a, b, c): print 'parametr dekoratora a =', a print 'przed wejściem do ', f.__name__ f(a, b, c) print 'po wyjściu z ', f.__name__ return inner @decorator(5) def g(a, b, c): print 'wewnątrz g: a =', a, 'b =', b, 'c =', c g(1, 2, 3) Tym razem w momencie dekorowania zostanie wywołany konstruktor klasy dekorator i metoda __call__ (będzie ona wywoływana tylko podczas dekorowania), a jej wynik zostanie przypisany na zmienną przechowującą dekorowaną funkcję. Przy wywołaniu funkcji g będzie wywoływana funkcja inner. Jeśli chcemy zrealizować analogiczną konstrukcję przy pomocy funkcji to dekorator musi być funkcją, która przyjmuje parametry dekoratora i zwraca funkcję, która przyjmuje dokładnie jeden argument - funkcję dekorowaną i zwraca funkcję, która ma być podstawiona w miejsce funkcji dekorowanej: def decorator(a): def wrapper(f): def inner(a, b, c): print 'parametr dekoratora a =', a print 'przed wejściem do ', f.__name__ f(a, b, c) print 'po wyjściu z ', f.__name__ return inner return wrapper @decorator(5) def g(a, b, c): print 'wewnątrz g: a =', a, 'b =', b, 'c =', c g(1, 2, 3) W tym przypadku podczas dekorowania zostanie wywołana funkcja decorator z argumentem 5 i funkcja wrapper z argumentem g, wywołując później funkcję g będziemy wywoływali funkcję inner. W Pythonie mamy dwa wbudowane dekoratory - classmethod i staticmethod - służą one do definiowania metod w klasach, które nie wymagają instancji klasy do ich wywołania - mogą być wywoływane przez klasę - metody udekorowane classmetod jako pierwszy parametr otrzymują klasę na rzecz której zostały wywołane, a staticmethod nie wiedzą nawet na rzecz jakiej klasy zostały wywołane: class A(object): def normalna(self): print "metoda normalna, wywołana na obiekcie", self @classmethod def klasowa(cls): print "metoda klasowa, wywołana na klasie", cls @staticmethod def statyczna(): print "metoda statyczna" A.klasowa() A.statyczna() A().normalna() Zauważ, że do wywołania metod statyczna() i klasowa() nie potrzebowaliśmy tworzyć obiektu klasy A, z kolei próba wywołania metody normalna() na klasie powoduje wyjątek: >>> A.normalna() Traceback (most recent call last): File "<pyshell#195>", line 1, in <module> A.normalna() TypeError: unbound method normalna() must be called with A instance as first argument (got nothing instead) Dekoratory można też wykorzystywać do modyfikowania klas, jeśli na przykład chcemy dodać zmienną klasową a i metodę b do klasy A możemy stworzyć następujący dekorator: def decorator(C): C.a = 5 def b(self): print self C.b = b return C @decorator class A(object): pass a = A() a.b() print a.a Funkcje i klasy można dekorować wieloma dekoratorami na raz: def A(f): def inner(): print 'dekorator A' f() return inner def B(f): def inner(): print 'dekorator B' f() return inner @A @B def f(): print 'funkcja f' f() Zadanie 1 Napisz dekorator modelujący pamięć dla funkcji, tak aby gdy powtórnie wywołamy ją z jakimiś parametrami funkcja nie była wywoływana i zwracana była wartość jaką otrzymano wcześniej dla tych samych argumentów. Zadanie 2 Napisz dekorator do funkcji zwracającej wartość logiczną wywołujący funkcję do czasu gdy zwróci True. Przetestuj na funkcji czytającej z wejścia i sprawdzającej czy użytkownik wpisał 'python'. Niech dekorator jako argument przyjmuje liczbę całkowitą oznaczającą maksymalną liczbę prób wywołania funkcji dekorowanej. Zadanie 3 Napisz dekorator, który będzie wymagał podania hasła przed właściwym wywołaniem funkcji, jeśli zostanie podane błędne hasło to niech będzie wypisany komunikat o braku dostępu. Zadanie 4 Napisz dekorator rejestrujący funkcje do listy będącej parametrem dekoratora, a następnie wywołaj wszystkie funkcje z listy "Programowanie dla Fizyków Medycznych"