Jak mogę zrobić dwa dekoratory w Pythonie, które wykonałyby następujące czynności?
@makebold
@makeitalic
def say():
return "Hello"
... które powinny zwrócić:
"<b><i>Hello</i></b>"
Nie próbuję HTML
tego zrobić w prawdziwej aplikacji - po prostu próbuję zrozumieć, jak działają dekoratorzy i łączenie dekoratorów.
__name__
i mówiąc o pakiecie dekoratora, podpis funkcji).*args
i**kwargs
należy je dodać w odpowiedzi. Udekorowana funkcja może mieć argumenty i zostaną one utracone, jeśli nie zostaną określone.*args
,**kwargs
. Łatwym sposobem rozwiązania tych 3 problemów na raz jest użycie,decopatch
jak wyjaśniono tutaj . Możesz także użyć,decorator
jak już wspomniano Marius Gedminas, aby rozwiązać punkty 2 i 3.Jeśli nie znasz długich wyjaśnień, zobacz odpowiedź Paolo Bergantino .
Podstawy dekoratora
Funkcje Pythona są obiektami
Aby zrozumieć dekoratory, musisz najpierw zrozumieć, że funkcje są obiektami w Pythonie. Ma to ważne konsekwencje. Zobaczmy dlaczego na prostym przykładzie:
Pamiętaj o tym. Wkrótce do niego wrócimy.
Kolejną interesującą właściwością funkcji Pythona jest to, że można je zdefiniować w innej funkcji!
Referencje funkcji
OK, wciąż tu jesteś? Teraz część zabawy ...
Widziałeś, że funkcje są obiektami. Dlatego funkcje:
Oznacza to, że funkcja może być
return
inną funkcją .Jest więcej!
Jeśli potrafisz
return
funkcję, możesz przekazać ją jako parametr:Cóż, masz wszystko, czego potrzeba, aby zrozumieć dekoratorów. Widzisz, dekoratory są „opakowaniami”, co oznacza, że umożliwiają wykonywanie kodu przed i po dekorowaniu funkcji bez modyfikowania samej funkcji.
Ręcznie wykonane ozdoby
Jak zrobiłbyś to ręcznie:
Teraz prawdopodobnie chcesz, aby za każdym razem, gdy dzwonisz
a_stand_alone_function
,a_stand_alone_function_decorated
jest wywoływany zamiast. To proste, po prostu zastąpa_stand_alone_function
funkcję zwracaną przezmy_shiny_new_decorator
:Dekoratorzy zdemistyfikowani
Poprzedni przykład przy użyciu składni dekoratora:
Tak, to wszystko, to takie proste.
@decorator
to tylko skrót do:Dekoratorzy to tylko pythoniczny wariant wzoru dekoracyjnego . Istnieje kilka klasycznych wzorców projektowych osadzonych w Pythonie, aby ułatwić programowanie (np. Iteratory).
Oczywiście można gromadzić dekoratorów:
Korzystanie ze składni dekoratora Python:
Kolejność, w jakiej ustawiasz dekoratorów, ma znaczenie:
Teraz: aby odpowiedzieć na pytanie ...
Podsumowując, możesz łatwo zobaczyć, jak odpowiedzieć na pytanie:
Teraz możesz po prostu wyjść szczęśliwy lub spalić trochę mózg i zobaczyć zaawansowane zastosowania dekoratorów.
Przenoszenie dekoratorów na wyższy poziom
Przekazywanie argumentów do dekorowanej funkcji
Metody zdobienia
Jedną fajną rzeczą w Pythonie jest to, że metody i funkcje są naprawdę takie same. Jedyną różnicą jest to, że metody oczekują, że ich pierwszym argumentem jest odwołanie do bieżącego obiektu (
self
).Oznacza to, że możesz zbudować dekorator dla metod w ten sam sposób! Pamiętaj tylko, aby wziąć
self
pod uwagę:Jeśli tworzysz dekorator ogólnego zastosowania - taki, który zastosujesz do dowolnej funkcji lub metody, bez względu na jej argumenty - użyj po prostu
*args, **kwargs
:Przekazywanie argumentów dekoratorowi
Świetnie, a co powiesz na przekazywanie argumentów samemu dekoratorowi?
Może to być nieco przekręcone, ponieważ dekorator musi zaakceptować funkcję jako argument. Dlatego nie można przekazać argumentów funkcji dekorowanej bezpośrednio do dekoratora.
Zanim pospieszysz do rozwiązania, napiszmy krótkie przypomnienie:
Jest dokładnie tak samo.
my_decorator
Nazywa się „ ”. Kiedy więc@my_decorator
mówisz Pythonowi, aby wywołał funkcję „oznaczoną zmienną„my_decorator
”.To jest ważne! Etykieta, którą podajesz, może wskazywać bezpośrednio na dekoratora - lub nie .
Zdobądźmy zło. ☺
Nic dziwnego.
Zróbmy DOKŁADNIE to samo, ale pomiń wszystkie nieznośne zmienne pośrednie:
Zróbmy jeszcze krótszy :
Hej, widziałeś to? Użyliśmy wywołania funkcji ze
@
składnią „ ”! :-)Wróćmy więc do dekoratorów z argumentami. Jeśli możemy użyć funkcji do generowania dekoratora w locie, możemy przekazać argumenty do tej funkcji, prawda?
Oto on: dekorator z argumentami. Argumenty można ustawić jako zmienne:
Jak widać, można przekazywać argumenty do dekoratora, jak każda funkcja korzystająca z tej sztuczki. Możesz nawet użyć,
*args, **kwargs
jeśli chcesz. Pamiętaj jednak, że dekoratorzy są nazywani tylko raz . Właśnie wtedy, gdy Python importuje skrypt. Nie można później dynamicznie ustawiać argumentów. Kiedy „importujesz x”, funkcja jest już dekorowana , więc nie możesz nic zmienić.Przećwiczmy: dekorowanie dekoratora
Dobra, jako bonus dam ci fragment, aby każdy dekorator ogólnie zaakceptował każdy argument. W końcu, aby zaakceptować argumenty, stworzyliśmy naszego dekoratora za pomocą innej funkcji.
Owinęliśmy dekoratora.
Czy jeszcze coś, co widzieliśmy ostatnio w tej opakowanej funkcji?
O tak, dekoratorzy!
Zabawmy się i napisz dekorator dla dekoratorów:
Można go używać w następujący sposób:
Wiem, kiedy ostatnio miałeś to uczucie, to po wysłuchaniu faceta, który powiedział: „zanim zrozumiesz rekurencję, musisz najpierw zrozumieć rekurencję”. Ale czy nie czujesz się dobrze z opanowaniem tego?
Najlepsze praktyki: dekoratorzy
functools
Moduł wprowadzono Pythonie 2.5. Zawiera funkcjęfunctools.wraps()
, która kopiuje nazwę, moduł i dokumentację dekorowanej funkcji do opakowania.(Ciekawostka:
functools.wraps()
jest dekoratorem!)W jaki sposób dekoratorzy mogą być przydatni?
Teraz najważniejsze pytanie: do czego mogę używać dekoratorów?
Wydają się fajne i potężne, ale praktyczny przykład byłby świetny. Istnieje 1000 możliwości. Klasyczne zastosowania rozszerzają zachowanie funkcji z zewnętrznej biblioteki lib (nie można jej modyfikować) lub do debugowania (nie chcesz jej modyfikować, ponieważ jest tymczasowa).
Możesz ich użyć do rozszerzenia kilku funkcji w sposób DRY, np .:
Oczywiście dobrą rzeczą w dekoratorach jest to, że można ich używać od razu na prawie wszystkim bez konieczności przepisywania. SUCHO, powiedziałem:
Sam Python dostarcza kilku dekoratorów:
property
,staticmethod
, itd.To naprawdę duży plac zabaw.
źródło
__closure__
atrybut), aby wyciągnąć oryginalną funkcję nieudekorowaną. W tej odpowiedzi udokumentowano jeden przykład użycia, który obejmuje, w jaki sposób można wstrzyknąć funkcję dekoratora na niższym poziomie w ograniczonych okolicznościach.@decorator
Składnia Pythona jest najprawdopodobniej najczęściej używana do zastąpienia funkcji zamknięciem opakowania (jak opisuje odpowiedź). Ale może również zastąpić funkcję czymś innym. Polecenie wbudowaneproperty
,classmethod
istaticmethod
dekoratorzy zastąpić funkcję z deskryptora, na przykład. Dekorator może również zrobić coś z funkcją, na przykład zapisać odniesienie do niego w jakimś rejestrze, a następnie zwrócić go bez zmian, bez opakowania.Alternatywnie możesz napisać funkcję fabryczną, która zwraca dekorator, który otacza wartość zwracaną dekorowanej funkcji w znaczniku przekazanym do funkcji fabrycznej. Na przykład:
Umożliwia to pisanie:
lub
Osobiście napisałbym dekoratora nieco inaczej:
co dałoby:
Nie zapomnij o konstrukcji, dla której składnia dekoratora jest skrótem:
źródło
def wrap_in_tag(*kwargs)
wtedy@wrap_in_tag('b','i')
Wygląda na to, że inni powiedzieli ci już, jak rozwiązać problem. Mam nadzieję, że pomoże ci to zrozumieć, czym są dekoratorzy.
Dekoratorzy to tylko cukier syntaktyczny.
To
rozwija się do
źródło
@decorator()
(zamiast@decorator
) jest to cukier syntaktycznyfunc = decorator()(func)
. Jest to również powszechna praktyka, gdy trzeba generować dekoratorów „w locie”Oczywiście możesz również zwrócić lambdy z funkcji dekoratora:
źródło
makebold = lambda f : lambda "<b>" + f() + "</b>"
makebold = lambda f: lambda: "<b>" + f() + "</b>"
makebold = lambda f: lambda *a, **k: "<b>" + f(*a, **k) + "</b>"
functools.wraps
, aby nie odrzucać dokumentu / podpisu / nazwysay
@wraps
czegoś innego na tej stronie nie pomoże mi, gdy wydrukujęhelp(say)
i otrzymam „Pomoc na temat funkcji <lambda>` zamiast „Pomoc na temat funkcji powiedz” .Dekoratory Python dodają dodatkową funkcjonalność do innej funkcji
Dekorator kursywa może być jak
Zauważ, że funkcja jest zdefiniowana wewnątrz funkcji. Zasadniczo robi to zamianę funkcji na nową. Na przykład mam tę klasę
Powiedzmy teraz, że chcę, aby obie funkcje wypisały „---” po i przed ich wykonaniem. Mógłbym dodać nadruk „---” przed każdym wyciągiem i po nim. Ale ponieważ nie lubię się powtarzać, zrobię dekoratora
Teraz mogę zmienić klasę na
Więcej informacji na temat dekoratorów można znaleźć na stronie http://www.ibm.com/developerworks/linux/library/l-cpdecor.html
źródło
self
Argument jest potrzebny, ponieważnewFunction()
zdefiniowany waddDashes()
został specjalnie zaprojektowany jako dekorator metod, a nie dekorator funkcji ogólnych.self
Argumentem reprezentuje instancję klasy i jest przekazywane do metod klasy, czy go używać, czy nie - patrz rozdział zatytułowany Dekorowanie metod w odpowiedzi @ e-satis użytkownika.functools.wraps
Państwo mogłoby zrobić dwa oddzielne dekoratorów, że to, co chcesz, jak pokazano bezpośrednio poniżej. Zwróć uwagę na użycie
*args, **kwargs
w deklaracjiwrapped()
funkcji, która obsługuje dekorowaną funkcję posiadającą wiele argumentów (co nie jest tak naprawdę konieczne w przypadkusay()
funkcji przykładowej , ale jest uwzględnione dla ogólnej).Z podobnych powodów
functools.wraps
dekorator służy do zmiany meta atrybutów zawiniętej funkcji na te, które są dekorowane. Powoduje to, że komunikaty o błędach i dokumentacja funkcji osadzonych (func.__doc__
) są tymi z dekorowanej funkcji zamiast zwrapped()
.Udoskonalenia
Jak widać, w tych dwóch dekoratorach jest dużo zduplikowanego kodu. Biorąc pod uwagę to podobieństwo, byłoby lepiej, gdybyś zamiast tego stworzył ogólną, która faktycznie była fabryką dekoratorów - innymi słowy, funkcję dekoratora, która czyni innych dekoratorów. W ten sposób powtarzanie kodu będzie mniejsze - i pozwoli się przestrzegać zasady DRY .
Aby kod był bardziej czytelny, możesz przypisać bardziej opisową nazwę do generowanych fabrycznie dekoratorów:
lub nawet połączyć je w ten sposób:
Wydajność
Podczas gdy powyższe przykłady działają poprawnie, wygenerowany kod wiąże się z dużym nakładem pracy w postaci zewnętrznych wywołań funkcji, gdy stosuje się wiele dekoratorów jednocześnie. Może to nie mieć znaczenia, w zależności od dokładnego użycia (które może być na przykład związane z We / Wy).
Jeśli ważna jest szybkość dekorowanej funkcji, narzut można ograniczyć do pojedynczego wywołania dodatkowej funkcji, pisząc nieco inną funkcję fabryczną dekoratora, która implementuje dodawanie wszystkich tagów jednocześnie, dzięki czemu może generować kod, który pozwala uniknąć dodatkowych wywołań funkcji za pomocą oddzielnych dekoratorów dla każdego tagu.
Wymaga to więcej kodu w samym dekoratorze, ale działa to tylko wtedy, gdy jest stosowane do definicji funkcji, a nie później, gdy same są wywoływane. Dotyczy to również tworzenia bardziej czytelnych nazw przy użyciu
lambda
funkcji, jak pokazano wcześniej. Próba:źródło
Inny sposób robienia tego samego:
Lub bardziej elastycznie:
źródło
functools.update_wrapper
, aby utrzymaćsayhi.__name__ == "sayhi"
Chcesz wywołać następującą funkcję:
Wracać:
Proste rozwiązanie
Aby to zrobić po prostu, utwórz dekoratory, które zwracają lambdas (funkcje anonimowe), które zamykają funkcję (zamknięcia) i wywołują ją:
Teraz używaj ich zgodnie z potrzebami:
i teraz:
Problemy z prostym rozwiązaniem
Ale wydaje się, że prawie straciliśmy pierwotną funkcję.
Aby to znaleźć, musielibyśmy kopać w zamknięciu każdej lambda, z których jedna jest zakopana w drugiej:
Więc jeśli umieściliśmy dokumentację na temat tej funkcji lub chcielibyśmy móc udekorować funkcje, które wymagają więcej niż jednego argumentu, lub po prostu chcieliśmy wiedzieć, jakiej funkcji szukaliśmy w sesji debugowania, musimy zrobić nieco więcej z naszym obwoluta.
W pełni funkcjonalne rozwiązanie - przezwyciężenie większości tych problemów
Mamy dekorator
wraps
zfunctools
modułu w standardowej bibliotece!Szkoda, że wciąż jest jakiś kocioł, ale jest to tak proste, jak tylko możemy.
W Pythonie 3 domyślnie dostajesz
__qualname__
i__annotations__
przypisujesz.Więc teraz:
I teraz:
Wniosek
Widzimy więc, że
wraps
powoduje to, że funkcja zawijania robi prawie wszystko oprócz tego, że mówi nam dokładnie, co funkcja przyjmuje za argumenty.Istnieją inne moduły, które mogą próbować rozwiązać problem, ale rozwiązania nie ma jeszcze w standardowej bibliotece.
źródło
Aby wyjaśnić dekorator w prosty sposób:
Z:
Kiedy zrobić:
Naprawde robisz:
źródło
Dekorator przyjmuje definicję funkcji i tworzy nową funkcję, która wykonuje tę funkcję i przekształca wynik.
jest równa:
Przykład:
To
jest równoważne z tym
65 <=> „a”
Aby zrozumieć dekoratora, należy zauważyć, że dekorator stworzył nową funkcję do, która jest wewnętrzna, która wykonuje funkcję i przekształca wynik.
źródło
print(do(65))
iprint(do2(65))
byćA
iA
?Możesz także napisać dekorator w klasie
źródło
Na tę odpowiedź już dawno udzielono odpowiedzi, ale pomyślałem, że podzielę się moją klasą Dekoratorów, dzięki czemu pisanie nowych dekoratorów jest łatwe i kompaktowe.
Po pierwsze, myślę, że dzięki temu zachowanie dekoratorów jest bardzo jasne, ale ułatwia także bardzo zwięzłe definiowanie nowych dekoratorów. W powyższym przykładzie możesz go rozwiązać jako:
Możesz go również użyć do wykonywania bardziej złożonych zadań, takich jak na przykład dekorator, który automatycznie powoduje, że funkcja jest rekurencyjnie stosowana do wszystkich argumentów w iteratorze:
Które wydruki:
Zauważ, że w tym przykładzie nie uwzględniono
list
typu w wystąpieniu dekoratora, więc w końcowej instrukcji print metoda jest stosowana do samej listy, a nie do elementów listy.źródło
Oto prosty przykład łączenia dekoratorów. Zwróć uwagę na ostatni wiersz - pokazuje, co dzieje się pod przykryciem.
Dane wyjściowe wyglądają następująco:
źródło
Mówiąc o liczniku - jak podano powyżej, licznik będzie współużytkowany przez wszystkie funkcje korzystające z dekoratora:
W ten sposób dekorator może zostać ponownie użyty do różnych funkcji (lub użyty do wielokrotnego dekorowania tej samej funkcji:)
func_counter1 = counter(func); func_counter2 = counter(func)
, a zmienna licznika pozostanie prywatna dla każdej z nich.źródło
Udekoruj funkcje inną liczbą argumentów:
Wynik:
źródło
def wrapper(*args, **kwargs):
ifn(*args, **kwargs)
.Odpowiedź Paolo Bergantino ma tę wielką zaletę, że używa tylko stdlib i działa w tym prostym przykładzie, w którym nie ma argumentów dekoratora ani argumentów funkcji dekoracyjnych .
Ma jednak 3 główne ograniczenia, jeśli chcesz rozwiązać bardziej ogólne przypadki:
makestyle(style='bold')
dekoratora nie jest trywialne.@functools.wraps
nie zachowują podpisu , więc jeśli podane zostaną złe argumenty, zaczną działać i mogą spowodować inny rodzaj błędu niż zwykleTypeError
.@functools.wraps
jest uzyskać dostęp do argumentu opartego na jego nazwie . Rzeczywiście, argument może pojawić się w*args
, w**kwargs
lub może nie pojawić się wcale (jeśli jest opcjonalny).Napisałem,
decopatch
aby rozwiązać pierwszy problem, i napisałem,makefun.wraps
aby rozwiązać pozostałe dwa. Zauważ, żemakefun
wykorzystuje tę samą sztuczkę, co słynnadecorator
lib.W ten sposób stworzyłbyś dekorator z argumentami, zwracając naprawdę zachowujące podpisy opakowania:
decopatch
udostępnia dwa inne style programowania, które ukrywają lub pokazują różne koncepcje języka Python, w zależności od preferencji. Najbardziej kompaktowy styl jest następujący:W obu przypadkach możesz sprawdzić, czy dekorator działa zgodnie z oczekiwaniami:
Szczegółowe informacje można znaleźć w dokumentacji .
źródło