Powrót do: Podstawy Testów Automatycznych w Selenium i Python cz. 5 – Profesjonalna konfiguracja projektu
Dekoratory – Twój pierwszy dekorator z parametrami
Pierwszy dekorator już za nami. Teraz, aby uzupełnić wiedzę o podstawach na ich temat, przyszedł czas na poznanie sposobu jak przekazywać parametry.
Małe przypomnienie z zakresu argumentów i parametrów
Przypomnijmy czym są argumenty i związane z nimi parametry.
Parametrami nazywamy wszystkie pola wymagane przez naszą funkcję w chwili jej deklarowania. Przykład funkcji z jednym parametrem (name
):
def print_greetings(name): print(f'Hello {name}!')
Argumentami nazywamy wartości przekazywane do funkcji w chwili jej użycia. Przykład podania argumentu dla wspomnianej funkcji ('Przemek'
):
print_greetings('Przemek')
Czyli funkcja posiada parametry (jej deklaracja) lub przyjmuje argumenty (jej użycie). Dodatkowo przypomnijmy, że możemy ujawnić do jakiego parametru przypisujemy argument:
print_greetings(name='Przemek')
Wracamy do dekorowania
Stwórzmy kolejny skrypt typu scratches (Otwieranie widoku Scratches i tworzenie pliku Scratches). My nazwiemy go decorators_with_params i skopiujmy do niego kod z lekcji o skróconej metodzie dekorowania (z @), czyli:
import datetime def simple_current_time_wrapper(function): def wrapper(): print(datetime.datetime.now()) function() return wrapper @simple_current_time_wrapper def print_hello_jaktestowac(): print('Hello jaktestowac!') print_hello_jaktestowac() # more code... print_hello_jaktestowac()
Załóżmy, że chcielibyśmy zmienić funkcję print_hello_jaktestowac
na print_greetings
, która posiadałaby jeden parametr name
i wypisywałaby na konsoli print(f'Hello {name}!')
. Przy tych założeniach nasza nowa funkcja przybierze postać:
def print_greetings(name): print(f'Hello {name}!')
Zmieńmy nasz główny kod, dodajmy tam nową funkcję oraz jej wywołania:
import datetime def simple_current_time_wrapper(function): def wrapper(): print(datetime.datetime.now()) function() return wrapper @simple_current_time_wrapper def print_greetings(name): print(f'Hello {name}!') print_greetings('Jack') # more code... print_greetings('Bob')
Co otrzymamy po uruchomieniu naszego skryptu? Piękny, soczysty błąd 😉
Traceback (most recent call last): File "C:/Users/jaktestowac/.PyCharmCE2018.3/config/scratches/decorators_with_params.py", line 15, in module print_greetings('Jack') TypeError: wrapper() takes 0 positional arguments but 1 was given Process finished with exit code 1
Co możemy z niego wyczytać? Rzućmy okiem na tę część traceback z podaną ścieżką do miejsca obserwacji problemu.
File "C:/Users/jaktestowac/.PyCharmCE2018.3/config/scratches/decorators_with_params.py", line 15, in module print_greetings('Jack')
Gdzie wystąpił błąd? W linii 15
, przy użyciu naszej udekorowanej funkcji print_greetings('Jack')
. Hmm co zatem mówi nam ostatnia linia:
TypeError: wrapper() takes 0 positional arguments but 1 was given
Tu jest już ciekawiej i możemy stwierdzić, że tak naprawdę błąd wynika z innego miejsca. Z tego gdzie znajduje się funkcja wrapper()
. Tłumacząc prosto tekst błędu: funkcja wrapper()
nie przyjmuje argumentów a my jej na siłę jakieś wkładamy. Upewniamy się czy tak jest. Przechodzimy do miejsca gdzie deklarujemy funkcję wrapper
, czyli linii 5
w naszym kodzie. Poniżej widzisz fragment gdzie znajduje się wrapper
:
import datetime def simple_current_time_wrapper(function): def wrapper(): print(datetime.datetime.now())
Rzeczywiście, ta funkcja nie posiada wymaganych parametrów. No to mamy mały konflikt, bo funkcja print_greetings()
posiada wymagany argument.
Widzimy zatem, że problem leży w naszym dekoratorze. Dokładniej chodzi o brak wymaganego parametru w funkcji wewnętrznej wrapper()
. Zatem dodajmy go tam! Ale jak go nazwać? Najprościej podchodząc skopiujemy jego nazwę z naszej funkcji print_greetings()
. Czyli nasz def wrapper():
zamieni się w def wrapper(name):
. Uruchamiamy kod i mamy kolejny błąd:
File "C:/Users/jaktestowac/.PyCharmCE2018.3/config/scratches/decorators_with_params.py", line 15, in module print_greetings('Jack') File "C:/Users/jaktestowac/.PyCharmCE2018.3/config/scratches/decorators_with_params.py", line 7, in wrapper function() TypeError: print_greetings() missing 1 required positional argument: 'name'
Czytamy ostatnią wiadomość. Nasza funkcja print_greetings()
została wywołana bez argumentu? Przecież w jej deklaracji oraz w jej wywołaniu wszystko gra? Spójrzmy na dwie przedostatnie wiadomości stacktrace:
File "C:/Users/jaktestowac/.PyCharmCE2018.3/config/scratches/decorators_with_params.py", line 7, in wrapper function()
Linia w której wystąpił błąd w kodzie to linia nr 7
i jej treść jest nawet zaprezentowa w stacktrace.
function()
Czyli problemy znowu leżą w trzewiach naszego dekoratora.
No tak, jak już przekazaliśmy naszą funkcję do dekoratora to przecież mamy jej wywołanie w funkcji wrapper
. Sama funkcja wrapper
już posiada parametr name
. Jak możesz się domyślać, coś musimy jeszcze z tym parametrem zrobić. Najlepiej wrzucić go do naszej wywoływanej funkcji function()
, czyli function(name)
.
Cały skrypt po zmianach będzie miał postać:
import datetime def simple_current_time_wrapper(function): def wrapper(name): print(datetime.datetime.now()) function(name) return wrapper @simple_current_time_wrapper def print_greetings(name): print(f'Hello {name}!') print_greetings('Jack') # more code... print_greetings('Bob')
Po uruchomieniu powyższego skryptu dostaniemy oczekiwany wynik:
2019-01-02 16:56:32.518438 Hello Jack! 2019-01-02 16:56:32.518438 Hello Bob! Process finished with exit code 0
A gdybyśmy pominęli zapis @simple_current_time_wrapper
i spróbowali ręcznie udekorować funkcję? Nic prostszego! Całość niewiele będzie się różniła od tego co widzieliśmy w poprzednich lekcjach. Należy usunąć @simple_current_time_wrapper
a samo wywołanie naszej funkcji opakowującej będzie wyglądało w następujący sposób:
wrapper_print_greetings = simple_current_time_wrapper(print_greetings) wrapper_print_greetings('Jack')
Hmm. We wszystkich znanych mi opracowaniach argument i procedura opisywane są na odwrót w stosunku do tego dokumentu. Kto się myli ? Pierwszy lepszy wpis ze stacka “https://stackoverflow.com/questions/47169033/parameter-vs-argument-python”
Hej,
Faktycznie, jest tutaj paskudny błąd (z tego co widze to błd ten rozprzestrzenił się n całą lekcję, mimo faktu, że w słowniku do kursu jest poprawnie).
Dzięki wielkie za zgłoszenie 🙇 Zaraz wprowadzimy stosowane poprawki.
Pozdrawiam,