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.

Argumentami nazywamy wszystkie pola wymagane przez naszą funkcję w chwili jej deklarowania. Przykład funkcji z jednym argumentem (name):

def print_greetings(name):
   print(f'Hello {name}!')

Parametrami nazywamy wartości przekazywane do funkcji w chwili jej użycia. Przykład podania parametru dla wspomnianej funkcji ('Przemek'):

print_greetings('Przemek')

Czyli funkcja posiada argumenty (jej deklaracja) lub przyjmuje parametry (jej użycie). Dodatkowo przypomnijmy, że możemy ujawnić do jakiego argumentu przypisujemy parametr:

print_greetings(name='Przemek')
Wracamty do dekorowania

Stwórzmy kolejny skrypt typu 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 przyjmował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. Rzeczywiście, ta funkcja nie posiada argumentó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 argumentu 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 parametru? 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 argument name. Jak możesz się domyślać, coś musimy jeszcze z tym argumentem 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')

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *