W komentarzu do tej odpowiedzi na inne pytanie ktoś powiedział, że nie jest pewien, co się functools.wraps
dzieje. Zadaję więc to pytanie, aby na StackOverflow zapisano go na przyszłość: co functools.wraps
dokładnie robi ?
W komentarzu do tej odpowiedzi na inne pytanie ktoś powiedział, że nie jest pewien, co się functools.wraps
dzieje. Zadaję więc to pytanie, aby na StackOverflow zapisano go na przyszłość: co functools.wraps
dokładnie robi ?
Kiedy używasz dekoratora, zamieniasz jedną funkcję na inną. Innymi słowy, jeśli masz dekoratora
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
wtedy kiedy powiesz
@logged
def f(x):
"""does some math"""
return x + x * x
to dokładnie to samo co mówienie
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
a twoja funkcja f
zostanie zastąpiona funkcją with_logging
. Niestety oznacza to, że jeśli to powiesz
print(f.__name__)
wydrukuje się, with_logging
ponieważ taka jest nazwa twojej nowej funkcji. W rzeczywistości, jeśli spojrzysz na dokumentację f
, będzie ona pusta, ponieważ with_logging
nie ma dokumentacji, a więc dokumentacja, którą napisałeś, już jej nie będzie. Ponadto, jeśli spojrzysz na wynik pydoc dla tej funkcji, nie zostanie wymieniony jako przyjmujący jeden argument x
; zamiast tego zostanie wymieniony jako wzięcie *args
i **kwargs
ponieważ to właśnie zajmuje się w usłudze logowanie.
Jeśli użycie dekoratora zawsze oznaczało utratę informacji o funkcji, byłby to poważny problem. Właśnie dlatego mamy functools.wraps
. Pobiera to funkcję używaną w dekoratorze i dodaje funkcjonalność kopiowania nad nazwą funkcji, dokumentacją, listą argumentów itp. A ponieważ wraps
sam jest dekoratorem, następujący kod robi to poprawnie:
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print(f.__name__) # prints 'f'
print(f.__doc__) # prints 'does some math'
functools.wraps
tej pracy, czy nie powinna to być po prostu część wzoru dekoratora? kiedy nie chcesz używać @wraps?@wraps
, aby wykonać różne typy modyfikacji lub adnotacji na kopiowanych wartościach. Zasadniczo jest to rozszerzenie filozofii Python, które jawne jest lepsze niż niejawne, a przypadki szczególne nie są na tyle wyjątkowe, by łamać reguły. (Kod jest znacznie prostszy, a język łatwiejszy do zrozumienia, jeśli@wraps
trzeba go podać ręcznie, zamiast korzystać z jakiegoś specjalnego mechanizmu rezygnacji.)Bardzo często używam klas, a nie funkcji, dla moich dekoratorów. Miałem z tym pewne problemy, ponieważ obiekt nie będzie miał tych samych atrybutów, jakich oczekuje się od funkcji. Na przykład obiekt nie będzie miał atrybutu
__name__
. Miałem z tym konkretny problem, który był dość trudny do wykrycia, gdy Django zgłaszał błąd „obiekt nie ma atrybutu__name__
” ”. Niestety, dla dekoratorów w stylu klasowym nie wierzę, że @wrap wykona zadanie. Zamiast tego stworzyłem podstawową klasę dekoratora taką:Ta klasa zastępuje wszystkie wywołania atrybutów dekorowanej funkcji. Możesz teraz utworzyć prosty dekorator, który sprawdza, czy podano 2 argumenty w następujący sposób:
źródło
@wraps
mówią doktorzy z ,@wraps
jest to po prostu funkcja wygody dlafunctools.update_wrapper()
. W przypadku dekoratora klas możesz wywoływaćupdate_wrapper()
bezpośrednio z__init__()
metody Tak więc, nie ma potrzeby tworzeniaDecBase
w ogóle, można po prostu to na__init__()
zprocess_login
linii:update_wrapper(self, func)
. To wszystko.Począwszy od python 3.5+:
Jest pseudonimem dla
g = functools.update_wrapper(g, f)
. Robi dokładnie trzy rzeczy:__module__
,__name__
,__qualname__
,__doc__
, i__annotations__
atrybuty zf
nag
. Ta domyślna lista jest dostępnaWRAPPER_ASSIGNMENTS
, można ją zobaczyć w źródle funools .__dict__
odg
wszystkich elementów zf.__dict__
. (patrzWRAPPER_UPDATES
w źródle)__wrapped__=f
atrybutg
Konsekwencją jest to, że
g
wydaje się mieć taką samą nazwę, dokumentację, nazwę modułu i podpis niżf
. Jedyny problem polega na tym, że w przypadku podpisu nie jest to prawdą: po prostuinspect.signature
domyślnie podąża za łańcuchami opakowań. Możesz to sprawdzić, korzystając zinspect.signature(g, follow_wrapped=False)
wyjaśnienia w dokumencie . Ma to irytujące konsekwencje:Signature.bind()
.Teraz jest trochę zamieszania między
functools.wraps
dekoratorami, ponieważ bardzo częstym przypadkiem użycia do programowania dekoratorów jest zawijanie funkcji. Ale oba są całkowicie niezależnymi koncepcjami. Jeśli chcesz zrozumieć różnicę, zaimplementowałem biblioteki pomocnicze dla obu: decopatch, aby łatwo pisać dekoratory, i makefun, aby zapewnić zachowujący podpis zamiennik@wraps
. Pamiętaj, żemakefun
polega na tej samej sprawdzonej sztuczce, co słynnadecorator
biblioteka.źródło
to jest kod źródłowy o opakowaniach:
źródło
Warunek: musisz wiedzieć, jak używać dekoratorów, a zwłaszcza z opakowaniami. Ten komentarz wyjaśnia to trochę jasno lub ten link również wyjaśnia to całkiem dobrze.
Ilekroć używamy np. @: Wraps, a następnie własnej funkcji otoki. Zgodnie ze szczegółami podanymi w tym linku mówi to
Dekorator @wraps faktycznie wywołuje funkcję funools.partial (func [, * args] [, ** keywords]).
Mówi to definicja funkcji funools.partial ()
Co prowadzi mnie do wniosku, że @wraps wywołuje funkcję replace () i przekazuje jej funkcję otoki jako parametr. Funkcja częściowa () na końcu zwraca wersję uproszczoną, tj. Obiekt tego, co znajduje się w funkcji opakowania, a nie samą funkcję opakowania.
źródło
Krótko mówiąc, funkools.wraps to zwykła funkcja. Rozważmy ten oficjalny przykład . Za pomocą kodu źródłowego możemy zobaczyć więcej szczegółów na temat implementacji i uruchomionych kroków w następujący sposób:
Sprawdzając implementację __call__ , widzimy, że po tym kroku opakowanie (po lewej stronie) staje się obiektem wynikającym z self.func (* self.args, * args, ** newke words) Sprawdzając tworzenie O1 w __new__ , my wiem self.func to funkcja update_wrapper . Używa parametru * args , opakowania po prawej stronie , jako swojego pierwszego parametru. Sprawdzając ostatni krok update_wrapper , można zobaczyć, czy opakowanie po prawej stronie zostało zwrócone, a niektóre atrybuty zmodyfikowane w razie potrzeby.
źródło