jesteś tutaj�
401
Dekoratory funkcji
Ostatni krok: obsługa argumentów
Dotarliśmy niemal do celu — „bebechy” kodu naszego dekoratora są już na swoim
miejscu. Pozostało już tylko zapewnić, aby dekorator prawidłowo obsługiwał argumenty
dekorowanej funkcji bez względu na to, jakie miałyby one być. Przypomnij sobie 4.
punkt przepisu:
def check_logged_in(func):
def wrapper( ):
if ‘logged_in’ in session:
return func( )
return ‘NIE jesteś zalogowany.’
return wrapper
Co powinieneś dodać
do sygnatury funkcji
wrapper?
Gdy dekorator zostaje zastosowany do istniejącej funkcji, wszystkie jej wywołania
są
zastępowane
przez wywołania funkcji zwracanej przez dekorator. Jak
przekonałeś się już podczas przeglądania zamieszczonego na poprzedniej stronie
rozwiązania, aby spełnić wymogi stawiane w punkcie 3. przepisu tworzenia
dekoratora, zwróciliśmy opakowaną wersję istniejącej funkcji, która to wersja
zawiera dodatkowy kod implementujący niezbędne działania. Ta opakowana
wersja
dekoruje istniejącą funkcję.
Mamy tu jednak pewien problem, ponieważ samo zapewnienie opakowania
nie wystarczy; zachowane powinny być również
właściwości wywołania
dekorowanej funkcji. Oznacza to, że jeśli Twoja istniejąca funkcja przyjmowała
dwa argumenty, funkcja opakowana również powinna to robić. Gdybyś z góry
wiedział, ilu argumentów powinieneś się tu spodziewać, mógłbyś odpowiednio
zaprojektować swój kod. Niestety nie wiesz tego wcześniej, ponieważ dekorator
może zostać zastosowany do dowolnej istniejącej funkcji, która może mieć —
i to jak najbardziej dosłownie — dowolną liczbę i typ argumentów.
Co powinieneś zatem zrobić? Rozwiązaniem jest zapewnienie jak największej
„ogólności” i sprawienie, aby funkcja
wrapper
radziła sobie z obsługą dowolnej
liczby i typu argumentów. Wiesz już, jak to zrobić, ponieważ widziałeś, co można
osiągnąć za pomocą argumentów
*args
oraz
**kwargs
.
Dekorator zachowuje sygnaturę funkcji dekorowanej.
Dekorator musi zapewniać, że zwracana przez niego funkcja przyjmuje taką samą
liczbę i typy argumentów, jakich spodziewa się funkcja dekorowana.
4
Zaostrz ołówek
Zmieńmy funkcję wrapper w taki sposób, aby przyjmowała dowolną liczbę i typ argumentów.
Należy też zapewnić, aby funkcja func, gdy jest wywoływana, używała tej samej liczby i typu
argumentów, jakie zostały przekazane funkcji wrapper. W pustych miejscach widocznych
poniżej dodaj kod odpowiedzialny za argumenty.
Pamiętaj, że konstrukcje
*args oraz **kwargs
obsługują dowolną liczbę
i typ argumentów.
402
Rozdział 10.
Ukończony dekorator
Sprawa załatwiona... prawda?
Jeśli swoje wnioski wyciągasz na podstawie porównania tego, co zrobiliśmy, z tym, co
znajduje się w naszym przepisie, wybaczymy Ci wiarę w to, że nasze zadanie dobiegło
już końca. Dobiegło... prawie. Musimy sobie poradzić jeszcze z dwoma rzeczami: jedna
związana jest ze wszystkimi dekoratorami, podczas gdy druga wiąże się z tym konkretnym
dekoratorem.
Najpierw pozbądźmy się drugiego problemu, specyficznego dla tego przypadku. Jako że
dekorator
check_logged_in
znajduje się w osobnym module, musimy zapewnić, aby
wszystkie moduły, do których odwołujemy się w jego kodzie, były również zaimportowane
w pliku
checker.py. Dekorator ten wykorzystuje słownik
session
, który należy
zaimportować z frameworka Flask, aby uniknąć błędów. Można to zrobić bardzo łatwo,
ponieważ sprowadza się to do dodania przedstawionej poniżej instrukcji
import
na
samym początku pliku modułu.
from flask import session
Pierwszy problem, który dotyczy wszystkich dekoratorów, jest związany ze sposobem, w jaki
funkcje przedstawiają się interpreterowi. Po udekorowaniu i w sytuacji, gdy nie dołoży się
odpowiednich starań, funkcja może „utracić” swoją tożsamość, co może z kolei prowadzić
do kłopotów. Powód, dla którego tak się dzieje, ma bardzo techniczny charakter i jest
dość egzotyczny, aby zaś go zrozumieć, trzeba dysponować wiedzą na temat działania
wewnętrznych mechanizmów Pythona, której większość osób nie musi (ani nie chce)
posiadać. W związku z tym biblioteka standardowa Pythona zapewnia moduł obsługujący
odpowiednie szczegóły za Ciebie (dzięki czemu nigdy nie będziesz musiał się nimi
martwić). Powinieneś jedynie pamiętać o zaimportowaniu niezbędnego modułu (o nazwie
functools
), a następnie wywołaniu jednej należącej do niego funkcji (o nazwie
wraps
).
Być może zakrawa to na pewną ironię, ale funkcja
wraps
jest zaimplementowana jako
dekorator, dlatego tak naprawdę nie wywołujesz jej, lecz raczej używasz, aby udekorować
swoją funkcję
wrapper
wewnątrz własnego dekoratora. Poszliśmy o krok dalej i zrobiliśmy
to już za Ciebie. Uzupełniony kod dekoratora
check_logged_in
został zaprezentowany
na początku następnej strony.
Zaostrz ołówek
Rozwiązanie
def check_logged_in(func):
def wrapper( ):
if ‘logged_in’ in session:
return func( )
return ‘NIE jesteś zalogowany.’
return wrapper
*args, **kwargs
*args, **kwargs
Zastosowanie ogólnej
sygnatury rozwiązuje problem,
ponieważ zapewnia obsługę
dowolnej liczby i typu
argumentów. Zwróć uwagę,
że funkcję func wywołujemy
z tymi samym argumentami,
które zostały podane funkcji
wrapper, niezależnie od tego,
jakie są to argumenty.
Miałeś zmienić funkcję wrapper w taki sposób, aby przyjmowała dowolną liczbę i typ
argumentów, jak również zapewnić, aby funkcja func, gdy jest wywoływana, używała
tej samej liczby i typu argumentów, jakie zostały przekazane funkcji wrapper.
Tworząc własne
dekoratory, zawsze
importuj, a następnie
stosuj funkcję wraps
należącą do modułu
functools.
Get Python Rusz głową! Wydanie II now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.