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')
TIP: Ten temat opisaliśmy również w naszym słowniku – argumenty funkcji.

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')

2 komentarze

  1. 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”

    Avatar A
    1. 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,

      Krzysiek Kijas Krzysiek Kijas

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *