Powrót do: Podstawy Testów Automatycznych w Selenium i Python cz. 5 – Profesjonalna konfiguracja projektu
Pierwsza funkcja dekorująca
Nasz kod nie wygląda za dobrze. Ale z pomocą przychodzą nam dekoratory, które nie modyfikują oryginalnej funkcji/metody a jedynie rozszerzają jej działanie.
Brzmi to trochę skomplikowanie i od razu nasuwa się pytanie – jak to zrobić? Dekorator nie jest niczym innym jak zwykłą funkcją, która przyjmuje jako argument inną funkcję, którą ma opakować.
Funkcje można tak przekazywać?
Oczywiście! Funkcje mogą zachowywać się jak zwykłe obiekty. Tajemnica tkwi w podwójnych nawiasach, które stawiamy przy wywołaniu funkcji ()
😉 W następnym akapicie omawiamy związane z tym przypadki.
Przykładowo, możemy zastosować funkcję tak:
def print_hello_jaktestowac(): print('Hello jaktestowac!') print_hello_jaktestowac
Tutaj nic się nie stanie ale jak zrobimy:
def print_hello_jaktestowac(): print('Hello jaktestowac!') print(print_hello_jaktestowac)
Dostaniemy coś takiego:
<function print_hello_jaktestowac at 0x038AA8E8>
Czyli informację o obiekcie. Nasza funkcja bez ()
, czyli print_hello_jaktestowac
, zachowuje się jak zwykły obiekt. Możemy też na przykład przekazać ją do innej funkcji albo przypisać ją do jakiejś zmiennej i potem ją wywołać:
def print_hello_jaktestowac(): print('Hello jaktestowac!') hello = print_hello_jaktestowac hello()
Natomiast gdy zastosujemy ()
, czyli print_hello_jaktestowac()
to funkcja nasza zostanie wywołała a my dostaniemy wynik – wypisany napis.
return
?
print_hello_jaktestowac()
Wypisze nam na konsoli:
Hello jaktestowac!
Zauważmy, że sama funkcja print_hello_jaktestowac()
nic nie zwraca (nie zawiera return):
x = print_hello_jaktestowac() print(x)
Zwróci nam:
Hello jaktestowac! None
Dlaczego dostajemy None
? Gdyż w print_hello_jaktestowac
nie ma wyrażenia return
, a każda funkcja i metoda domyślnie zwraca wartość None
😉 Sprawdźmy – gdy dodamy do funkcji print_hello_jaktestowac
zwracaną wartość przez return
w taki sposób:
def print_hello_jaktestowac(): print('Hello jaktestowac!') return 'secret'
Powtarzając ćwiczenia z tego TIP otrzymamy nasz zwracany string zamiast None
. Mądrzejsi o tą wiedzę wracamy do poprzedniej wersji funkcji print_hello_jaktestowac
.
Funkcja dekoratora
Stwórzmy w takim razie pustą funkcję o nazwie simple_current_time_wrapper
. Zauważ, że już nazwa wskazuje, że będziemy opakowywać.
import datetime def simple_current_time_wrapper(): pass def print_hello_jaktestowac(): print('Hello jaktestowac!') print(datetime.datetime.now()) print_hello_jaktestowac() # more code... print(datetime.datetime.now()) print_hello_jaktestowac()
Czarów tutaj nie ma – lecimy dalej. Teraz w funkcji simple_current_time_wrapper
napiszemy wewnętrzną funkcję. Tu możesz zapytać:
A cóż to za abominacja?! Funkcja w funkcji?
Funkcja wewnętrzna
Python umożliwia tego typu konstrukcje, aczkolwiek ich przydatność jest ograniczona, gdyż z takich wewnętrznych funkcji można korzystać jedynie w obrębie zewnętrznej funkcji, w której zostały stworzone.
Poniższy fragment skryptu dobrze obrazuje tę sytuację:
def visit_town_innsmouth(): def enter_innsmouth_tavern(): print('You have entered the tavern.') def enter_innsmouth_hotel(): print('You have entered the hotel. Beware!') print('You have arrived to Innsmouth...') # użycie dozwolone wewnętrznej funkcji enter_innsmouth_tavern() enter_innsmouth_hotel() # użycie niedozwolone wewnętrznej funkcji - poza obrębem funkcji, w której została utworzona: enter_innsmouth_tavern()
Uruchomienie powyższego skryptu zwróci nam błąd dotyczący braku widoczności funkcji, którą chcemy wywołać:
NameError: name 'enter_innsmouth_tavern' is not defined
Nic dziwnego, w ostatniej linii skryptu następuje użycie wewnętrznej funkcji w niedozwolonym miejscu.
Funkcja wewnętrzna dekoratora
Wracamy do pisania kodu w simple_current_time_wrapper
. Nowa funkcja będzie miała nazwę wrapper
. Zadeklarujemy ją bez parametrów. Będzie to funkcja wewnętrzna, którą na razie zaślepimy za pomocą pass
:
import datetime def simple_current_time_wrapper(): def wrapper(): pass def print_hello_jaktestowac(): print('Hello jaktestowac!') print(datetime.datetime.now()) print_hello_jaktestowac() # more code... print(datetime.datetime.now()) print_hello_jaktestowac()
Dodatkowo na końcu simple_current_time_wrapper
zwróćmy nowo utworzoną funkcję wrapper
. Robimy to za pomocą wyrażenia return
i jej nazwy. Czyli zwracamy funkcję jako obiekt a nie rezultat jej wykonania, oznacza to, że zwrócimy ją bez ()
. Całość przyjmie postać:
import datetime def simple_current_time_wrapper(): def wrapper(): pass return wrapper def print_hello_jaktestowac(): print('Hello jaktestowac!') print(datetime.datetime.now()) print_hello_jaktestowac() # more code... print(datetime.datetime.now()) print_hello_jaktestowac()
Co to oznacza, że zwracamy tę funkcję jako obiekt?
Testujemy wyprodukowaną funkcję
W osobnym pliku scratches skopiuj kod samej funkcji simple_current_time_wrapper
:
def simple_current_time_wrapper(): def wrapper(): pass return wrapper
Następnie dodaj zamiast pass
np: print("Hello")
.
def simple_current_time_wrapper(): def wrapper(): print("Hello") return wrapper
I teraz wywołaj funkcję simple_current_time_wrapper
:
def simple_current_time_wrapper(): def wrapper(): print("Hello") return wrapper simple_current_time_wrapper()
Po uruchomieniu tego kodu nic się nie stanie. To dlatego, że tak naprawdę zwracamy tylko obiekt funkcji wrapper
. Trzeba ten obiekt dopiero przypisać do zmiennej aby coś z nim więcej zrobić:
def simple_current_time_wrapper(): def wrapper(): print("Hello") return wrapper func_as_object = simple_current_time_wrapper()
I mamy teraz w zmiennej func_as_object
zwróconą funkcję wrapper
. To powinno być jasne. Skoro func_as_object
to jest funkcja wrapper
to możemy ją wywołać. No i tym samym uzyskać wynik z funkcji wrapper
– dodajmy więc wywołanie funkcji!.
def simple_current_time_wrapper(): def wrapper(): print("Hello") return wrapper func_as_object = simple_current_time_wrapper() func_as_object()
Wywołanie funkcji w ostatniej linijce w końcu przynosi oczekiwany rezultat. Jest to niezbyt proste zagadnienie ale ćwicząc tego typu konstrukcje możesz już powoli poczuć o co chodzi z obiektem funkcji a samym jej wywołaniem.
Oczywiście nie tworzymy tych konstrukcji po to aby sobie poczarować w kodzie – ta wiedza jest niezbędna przy tworzeniu dekoratorów. Wracamy zatem do naszego poprzedniego kodu i kontynuujemy budowanie pierwszego dekoratora.
Kod funkcji wewnętrznej dekoratora
Przypomnijmy, mamy funkcję simple_current_time_wrapper
, która posiada funkcję wewnętrzną. Wywołanie simple_current_time_wrapper
zwraca funkcję wewnętrzną.
Już jesteśmy blisko. Teraz zastąpimy pass
kodem, który wypisze nam aktualną datę.
print(datetime.datetime.now())
Funkcja dekoratora przyjmie następującą postać:
def simple_current_time_wrapper(): def wrapper(): print(datetime.datetime.now()) return wrapper
Czyli w końcu pojawił się jakiś konkret w wewnętrznej funkcji.
Mini Teraz Ty – Test funkcji dekoratora
Możemy już przetestować wywołanie naszej funkcji.
- Skopiuj jej kod i wklej go do nowego pliku.
- Powtórz sprytny zabieg z przypisaniem zwracanej wartości do zmiennej.
- Potem wywołaj tą zmienną jako funkcję.
- Nie zapomnij o imporcie
datetime
.
Twoje zadanie to tak naprawdę dodanie kodu niezbędnego do tego, aby uruchomić wewnętrzną funkcję co umożliwi wypisanie daty na konsolę.
Mini rozwiązanie – Test funkcji dekoratora
Kod mógłby wyglądać tak:
import datetime def simple_current_time_wrapper(): def wrapper(): print(datetime.datetime.now()) return wrapper my_wrapper = simple_current_time_wrapper() my_wrapper()
Wynik na konsoli to oczywiście data 😀
Po udanym eksperymencie wróć do naszego głównego kodu.
Parametr funkcji dekoratora
Dodamy do naszej nowej funkcji jeden parametr o nazwie function
. Tak, funkcja simple_current_time_wrapper
jako parametr przyjmie obiekt funkcji którą chcemy opakować:
import datetime def simple_current_time_wrapper(function): def wrapper(): print(datetime.datetime.now()) return wrapper def print_hello_jaktestowac(): print('Hello jaktestowac!') print(datetime.datetime.now()) print_hello_jaktestowac() # more code... print(datetime.datetime.now()) print_hello_jaktestowac()
Czyli powtórzmy, parametrem jest obiekt funkcji – czyli taka nie wywołana jeszcze funkcja co umożliwia nam uruchomienie jej w dowolnym momencie w kodzie.
Na koniec, po print()
, w wewnętrznej funkcji dodamy wykonanie funkcji przekazanej w parametrze
.
def simple_current_time_wrapper(function): def wrapper(): print(datetime.datetime.now()) function() return wrapper
Test pełnej funkcji dekoratora
I znowu zróbmy szybki eksperyment w nowym pliku scratches. Skopiujmy nasz obecny wygląd funkcji:
import datetime def simple_current_time_wrapper(function): def wrapper(): print(datetime.datetime.now()) function() return wrapper
Dodajmy pod importem nową super prostą funkcję cat_sound()
:
import datetime def cat_sound(): print("miauuuu") def simple_current_time_wrapper(function): def wrapper(): print(datetime.datetime.now()) function() return wrapper
Wywołajmy simple_current_time_wrapper
i przypiszmy wynik tej funkcji do zmiennej.
cat_with_date = simple_current_time_wrapper()
To jeszcze za mało, wywołanie simple_current_time_wrapper
musi się odbyć z wymaganym parametrem. Jako parametr podaj nazwę nowej funkcji cat_sound
.
cat_with_date = simple_current_time_wrapper(cat_sound)
Czyli przekazaliśmy funkcję cat_sound
jako obiekt. I w wyniku wykonania simple_current_time_wrapper(cat_sound)
dostaliśmy obiekt funkcji wrapper
. Ten obiekt (kryjący się pod zmienną cat_with_date
) zawiera w sobie całą zawartość naszej funkcji wewnętrznej wrapper
: czyli print i wywołanie przekazanej w parametrze funkcji.
Czas uruchomić cat_with_date
! Dodajemy linię z wywołaniem tego obiektu – i nasz cały kod będzie wyglądał tak:
import datetime def cat_sound(): print("miauuuu") def simple_current_time_wrapper(function): def wrapper(): print(datetime.datetime.now()) function() return wrapper cat_with_date = simple_current_time_wrapper(cat_sound) cat_with_date()
Jaki jest wynik – nic zaskakującego. Wykonane zostało wszystko co siedzi w wrapper
czyli i czas i uruchomienie przekazanej funkcji w parametrze.
2019-01-17 08:51:28.742418 miauuuu
Finalny kod dekoratora
Wracamy do naszego bazowego kodu:
import datetime def simple_current_time_wrapper(function): def wrapper(): print(datetime.datetime.now()) function() return wrapper def print_hello_jaktestowac(): print('Hello jaktestowac!') print(datetime.datetime.now()) print_hello_jaktestowac() # more code... print(datetime.datetime.now()) print_hello_jaktestowac()
Tym samym uzyskaliśmy najprostszą funkcją dekorującą. Zwraca ona wewnętrzną funkcję, która wykonuje nasz dodatkowy kod a następnie wykonuje przekazaną w parametrze funkcję. Teraz pozostaje ją jakoś wykorzystać…
Zrobimy to dokładnie jak to było w przykładzie z miauczeniem kota 😀
co do wywołań obiektów funkcji to nie prościej zrobić to tak? tylko to juz nie jest wywołanie obiektu tylko samej funkcji?
i to tez zwroci nam Hello
Hej,
Przesłany kod jak najbardziej będzie działał w zaprezentowanej formie 😉 Ale w tym przypadku
'Hello'
wyświetlane jest w momencie wywołaniasimple_current_time_wrapper()
. Rzuć okiem na poniższy kod, w którym pomiędzy deklaracją funkcji a jej wywołaniem chcemy wykonać jakąś czynność (w tym przypadku – wypisaćBefore my hello_wrapper...
).Po uruchomieniu powyższego skryptu otrzymamy następujący wynik:
Wynik ten związany jest z faktem, że zawartość
wrapper
(czyli polecenie do wypisywania'Hello'
) wykonywana jest w momenciereturn wrapper()
(czyli wtedy gdy postawimy()
), czylireturn
tak na prawdę zwracaNone
. DlaczegoNone
? Gdyżdef wrapper():
nie zawiera w sobie wyrażeniareturn
, a jeślireturn
nie jest zawarty to zwracana jest właśnie wartośćNone
.Aby to naprawić, musimy opóźnić wywołanie funkcji
def wrapper
, która wywoływana jest wreturn wrapper()
. Pierwszym sposobem jest zmianareturn wrapper()
nareturn wrapper
. Dziki temu wywołanie funkcjidef wrapper
nastąpi w liniihello_wrapper()
.Również, jeśli zamienimy
hello_wrapper = simple_current_time_wrapper()
nahello_wrapper = simple_current_time_wrapper
to kod też zadziała poprawnie 🙂Mam nadzieję, że udało mi się odrobinę rozjaśnić tę kwestię 🙂
Pozdrawiam,