În articolul Deep Dive Into Python Decorators, am introdus conceptul de decoratori Python, am demonstrat mulți decoratori răcoritori și am explicat cum să le folosesc.
În acest tutorial vă voi arăta cum să vă scrieți propriile decoratoare. După cum veți vedea, scrierea propriilor decoratori vă oferă mult control și permite multe posibilități. Fără decoratori, aceste capacități ar necesita o mulțime de eroare-predispuse și boilerplate repetitive, care aglomera codul dvs. sau complet mecanisme externe cum ar fi generarea de cod.
O recapitulare rapidă dacă nu știți nimic despre decoratori. Un decorator este un callabil (funcție, metodă, clasă sau obiect cu a apel()) care acceptă o intrare callabilă și returnează o ieșire callabilă ca ieșire. În mod obișnuit, apelantul returnat face ceva înainte și / sau după ce a sunat la apelul de intrare. Aplicați decoratorul utilizând funcția @
Să începem cu o lume "Hello world!" decorator. Acest decorator va înlocui în totalitate orice decorat cu o funcțiune care imprimă "Hello World!".
python def hello_world (f): Def decorat (* args, ** kwargs): tipăriți 'Hello World!' retur decorat
Asta e. Să o vedem în acțiune și apoi să explicăm piesele diferite și cum funcționează. Să presupunem că avem următoarea funcție care acceptă două numere și imprimă produsul lor:
(x, y): imprima x * y
Dacă invocați, obțineți ceea ce vă așteptați:
înmulțiți (6, 7) 42
Să o decorăm cu noi Salut Lume decorator prin adnotarea multiplica funcția cu @Salut Lume
.
(x, y): print x * y
Acum, când suni multiplica cu argumente (inclusiv tipuri greșite de date sau număr greșit de argumente), rezultatul este întotdeauna "Hello World!" imprimate.
"python multiplica (6, 7) Hello World!
multiplica () Hello World!
multiplicați ("zzz") Hello World! "
O.K. Cum functioneazã? Funcția de multiplicare originală a fost complet înlocuită de funcția decorată în interior Salut Lume decorator. Dacă analizăm structura Salut Lume decorator, atunci veți vedea că acceptă intrarea apelată f (care nu este folosit în acest decorator simplu), definește o funcție imbricată numită decorate care acceptă orice combinație de argumente și argumente de cuvinte cheie (def decorat (* args, ** kwargs)
), și în cele din urmă returnează decorate funcţie.
Nu există nicio diferență între scrierea unei funcții și a unui decorator de metode. Definitia decoratorului va fi aceeasi. Avansul de intrare va fi fie o funcție obișnuită, fie o metodă legată.
Să verificăm asta. Aici este un decorator care imprimă doar intrarea callabilă și tastați înainte de ao invoca. Acest lucru este foarte tipic pentru ca un decorator să efectueze o anumită acțiune și să continue să invocă apelul inițial.
python def print_callable (f): def decorat (* args, ** kwargs): print f, tip (f) return f (* args, ** kwargs)
Rețineți ultima linie care invocă intrarea apelată într-un mod generic și returnează rezultatul. Acest decorator nu este intruziv în sensul că puteți decora orice funcție sau metodă într-o aplicație de lucru și aplicația va continua să funcționeze deoarece funcția decorată invocă originalul și are doar un efect secundar înainte.
Să o vedem în acțiune. Voi decora atât funcția noastră multiplă cât și o metodă.
"python @print_callable def multiplica (x, y): print x * y
clasa A (obiect): @print_callable def foo (self): print 'foo () aici "
Atunci când apelăm funcția și metoda, callabilul este imprimat și apoi își îndeplinesc sarcina inițială:
"multiplicarea python (6, 7)
A (). Foo ()
Decoratorii pot lua și argumente. Această abilitate de a configura funcționarea unui decorator este foarte puternică și vă permite să folosiți același decorator în multe contexte.
Să presupunem că codul dvs. este prea rapid și șeful dvs. vă cere să o încetiniți puțin pentru că faceți alți membri ai echipei să pară rău. Să scriem un decorator care măsoară cât timp funcționează o funcție și dacă rulează în mai puțin de un anumit număr de secunde T, acesta va aștepta până t expiră și apoi se va întoarce.
Ce este diferit acum este faptul că decoratorul însuși ia un argument T care determină durata minimă de rulare, iar funcțiile diferite pot fi decorate cu durate minime diferite. De asemenea, veți observa că atunci când introduceți argumente pentru decorator, sunt necesare două nivele de cuibărit:
"timpul de import pentru Python
def minimum_runtime (t): def decorat (f): def wrap (args, ** kwargs): start = time.time () rezultat = f (args, ** kwargs) runtime = time.time () - porni dacă este timpul de execuție < t: time.sleep(t - runtime) return result return wrapper return decorated"
Să-l despachetăm. Decoratorul însuși - funcția minimum_runtime ia un argument T, care reprezintă durata minimă de rulare pentru cei care au fost apelați. Intrarea este apelabilă f a fost "împins în jos" la imbricate decorate funcția și argumentele care pot fi apelate sunt "împinse" până la o altă funcție imbricată învelitoare.
Logica reală are loc în interiorul învelitoare funcţie. Timpul de pornire este înregistrat, originalul poate fi sunat f este invocat cu argumentele sale, iar rezultatul este stocat. Apoi, timpul de execuție este verificat și dacă este mai mic decât minimul T apoi doarme pentru restul timpului și apoi se întoarce.
Pentru a le testa, voi crea câteva funcții care se înmulțesc și le decorează cu întârzieri diferite.
"python @minimum_runtime (1) def slow_multiply (x, y): multiplica (x, y)
@minimum_runtime (3) def slower_multiply (x, y): multiplica (x, y) "
Acum, o să sun multiplica direct, precum și funcțiile mai lentă și măsurarea timpului.
"timpul de import pentru Python
funcția = [multiplicare, slow_multiply, slower_multiply] pentru funcțiile f: start = time.time () f (6, 7) print f, time.time ()
Aici este rezultatul:
neted 42
După cum puteți vedea, multiplicarea originală nu a durat aproape niciodată, iar versiunile mai lent au fost într-adevăr întârziate în funcție de durata de execuție minimă.
Un alt fapt interesant este că funcția decorată executată este ambalajul, ceea ce are sens dacă urmați definiția decorului. Dar asta ar putea fi o problemă, mai ales dacă avem de-a face cu decoratori de stivă. Motivul este că mulți decoratori își inspectează, de asemenea, intrarea lor și pot verifica numele, semnătura și argumentele. Următoarele secțiuni vor explora această problemă și vor oferi sfaturi pentru cele mai bune practici.
De asemenea, puteți utiliza obiecte ca decoratoare sau pentru a returna obiecte de la decoratorii dvs. Singura cerință este că aceștia au a __apel__() metodă, astfel încât acestea să poată fi sunrise. Iată un exemplu pentru un decorator bazat pe obiecte care numără de câte ori se numește funcția țintă:
(de exemplu, de exemplu, de exemplu, de exemplu, args, ** kwargs)
Aici este în acțiune:
"python @Counter def bbb (): tipăriți 'bbb'
bbb () bbb
bbb () bbb
bbb () bbb
print bbb.called 3 "
Aceasta este cea mai mare parte o chestiune de preferință personală. Funcțiile născute și funcțiile de închidere asigură toată gestionarea de stat oferită de obiecte. Unii oameni se simt mai acasă cu clase și obiecte.
În următoarea secțiune, voi discuta decoratori bine-comportați, iar decoratorii pe bază de obiect își vor lua puțină muncă suplimentară pentru a se comporta bine.
Decoratorii cu scop general pot fi adesea stivuite. De exemplu:
python @ decorator_1 @ decorator_2 def foo (): imprima 'foo () aici'
Când stivuiești decoratorii, decoratorul exterior (decorator_1 în acest caz) va primi callablele returnate de decoratorul interior (decorator_2). Dacă decorator_1 depinde în vreun fel de numele, argumentele sau docstring-ul funcției originale și decorator_2 este implementat naiv, atunci decorator_2 nu va vedea informațiile corecte din funcția originală, ci doar apelul invocat de decorator_2.
De exemplu, aici este un decorator care verifică numele funcției sale țintă este cu litere mici:
Python def check_lowercase (f): def decorat (* args, ** kwargs): afirmați f.func_name == f.func_name.lower () f (* args, ** kwargs)
Să decorăm o funcție cu ea:
python @check_lowercase def Foo (): imprima 'Foo () aici'
Apelarea Foo () are ca rezultat o afirmație:
"simplu In [51]: Foo () - Trasare asertionError (cel mai recent apel ultimul)