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,