TI/Programowanie dla Fizyków Medycznych:Dekoratory: Różnice pomiędzy wersjami

Z Brain-wiki
 
(Nie pokazano 3 wersji utworzonych przez 2 użytkowników)
Linia 75: Linia 75:
 
     def __call__(self, f):
 
     def __call__(self, f):
 
         def inner(a, b, c):
 
         def inner(a, b, c):
             print 'parametr dekoratora a =', a
+
             print 'parametr dekoratora a =', self.a
 
             print 'przed wejściem do ', f.__name__
 
             print 'przed wejściem do ', f.__name__
 
             f(a, b, c)
 
             f(a, b, c)
Linia 171: Linia 171:
 
f()
 
f()
 
</source>
 
</source>
 +
 +
===Zadanie 0===
 +
Napisz dekorator, który spowoduje, że przy wywołaniu udekorowanej funkcji wypisze się na ekran informacja po raz który ta funkcja jest wywoływana, np.: "Funkcja <nazwa funkcji> została uruchomiona <n> raz"
  
 
===Zadanie 1===
 
===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.
+
Napisz dekorator przechowujący w pamięci wynik wywołania funkcji z jakimś parametrem, tak aby gdy powtórnie wywołamy ją z tym samym parametrem, funkcja nie była wywoływana powtórnie, ale by zwracana była wartość jaką otrzymano wcześniej przy wywołaniu z tym parametrem.
 +
 
 
===Zadanie 2===
 
===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.
 
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.
Linia 180: Linia 184:
 
===Zadanie 4===
 
===Zadanie 4===
 
Napisz dekorator rejestrujący funkcje do listy będącej parametrem dekoratora, a następnie wywołaj wszystkie funkcje z listy
 
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"]]

Aktualna wersja na dzień 08:08, 10 lis 2017

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 =', self.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 0

Napisz dekorator, który spowoduje, że przy wywołaniu udekorowanej funkcji wypisze się na ekran informacja po raz który ta funkcja jest wywoływana, np.: "Funkcja <nazwa funkcji> została uruchomiona <n> raz"

Zadanie 1

Napisz dekorator przechowujący w pamięci wynik wywołania funkcji z jakimś parametrem, tak aby gdy powtórnie wywołamy ją z tym samym parametrem, funkcja nie była wywoływana powtórnie, ale by zwracana była wartość jaką otrzymano wcześniej przy wywołaniu z tym parametrem.

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"