Deplasați adânc în decoratorii Python

Prezentare generală

Decoratorii de la Python sunt una dintre funcțiile mele preferate Python. Acestea sunt cele mai ușor de utilizat * și * dezvoltator-prietenos punerea în aplicare a programării orientate spre aspect pe care l-am văzut în orice limbaj de programare. 

Un decorator vă permite să măriți, să modificați sau să înlocuiți complet logica unei funcții sau a unei metode. Această descriere uscată nu face justiția decoratorilor. Odată ce începeți să le utilizați, veți descoperi un întreg univers de aplicații îngrijite care vă ajută să păstrați codul strans și curat și să mutați sarcini importante "administrative" din fluxul principal al codului dvs. și într-un decorator. 

Înainte de a sari în câteva exemple reci, dacă doriți să explorați originea decoratorilor ceva mai mult, atunci decoratorii de funcții au apărut pe primul loc în Python 2.4. Vedeți PEP-0318 pentru o discuție interesantă despre istoria, rațiunea și alegerea numelui "decorator". Clasa decoratorilor a apărut prima în Python 3.0. Vedeți PEP-3129, care este destul de scurt și se bazează pe toate conceptele și ideile decoratorilor de funcții.

Exemple de decorațiuni răcoritoare

Există atât de multe exemple pe care le-am apăsat greu să aleg. Obiectivul meu aici este să vă deschidă mintea la posibilitățile și să vă prezint funcționalitatea super-utilă pe care o puteți adăuga la codul dvs. imediat prin adnotarea literală a funcțiilor dvs. printr-o singură linie.

Exemplele clasice sunt decoratorii încorporați @staticmethod și @classmethod. Acești decoratori transformă o metodă de clasă în mod corespunzător într-o metodă statică (nu este furnizat niciun argument prima de sine) sau o metodă de clasă (primul argument este clasa și nu instanța).

Decoratorii clasici

clasa A (obiect): @classmethod def foo (cls): print cls .__ nume__ @ staticmethod def bar (): print 'Nu am nici un folos pentru instanta sau clasa' A.foo () A.bar 

ieşire:

Nu am nici un folos pentru instanță sau clasă

Metodele statice și de clasă sunt utile atunci când nu aveți o instanță în mână. Ele sunt folosite foarte mult și a fost greoaie să le aplicăm fără sintaxa decoratorului.

Memoization

Decodorul @memoize amintește rezultatul primei invocări a unei funcții pentru un anumit set de parametri și o cachează. Invocările ulterioare cu aceiași parametri returnează rezultatul cache. 

Aceasta ar putea fi un amplificator de performanță imens pentru funcțiile care fac procesare scumpă (de exemplu, ajungerea la o bază de date la distanță sau apelarea mai multor API-uri REST) ​​și sunt numite adesea cu aceiași parametri.

@memoize def fetch_data (items): "" "Faci o muncă serioasă aici" "" result = "[fetch_item_data (i) pentru i în articole] 

Programare pe bază de contract

Ce zici de un cuplu de decoratori numit @ precondition and @postcondition pentru a valida argumentul de intrare, precum și rezultatul? Luați în considerare următoarea funcție simplă:

def add_small ints (a, b): "" "Adăugați două insule a căror sumă este încă int" "" returnați a + b

Dacă cineva o numește cu numere întregi mari sau lungi sau chiar și șiruri de caractere, va reuși liniștit, dar va încălca contractul că rezultatul trebuie să fie un int. Dacă cineva o numește cu tipuri de date nepotrivite, veți obține o eroare generică de execuție. Puteți adăuga următorul cod la funcția:

(a, int), 'a trebuie să fie un int') assert (isinstance (a, int) ), 'b trebuie să fie un int') rezultat = a + b afirma (isinstance (rezultat, int), argumentele sunt prea mari. 

Frumosul nostru unic add_small_ints () funcția a devenit doar o mlaștină urâtă cu afirmații urâte. Într-o funcție din lumea reală poate fi cu adevărat dificil să vezi dintr-o privire ceea ce face de fapt. Cu decoratori, condițiile pre și post se pot mișca din corpul funcției:

@interpretare (este o instanță (a, int), 'a trebuie să fie un int') @precondiție (esteinstance (b, int), 'b trebuie să fie un int' (a, b): "" "Adăugați două inturi în suma a cărei sumă este încă o int" "" returnați a + b 

Autorizare

Să presupunem că aveți o clasă care necesită autorizare printr-un secret pentru toate metodele sale multiple. Fiind dezvoltatorul desăvârșit al Python, probabil că veți opta pentru un decorator de metode autorizate ca în:

clasa SuperSecret (obiect): @ defautoautorizata def f_1 (* args, secrete): "" "" "" "@autorizat def f_2 (* args, secret) ): "" "" "" "

Aceasta este cu siguranta o abordare buna, dar este un pic enervant sa o faci repetitiv, mai ales daca ai multe astfel de clase. 

Mai critic, dacă cineva adaugă o nouă metodă și uită să adauge decorarea @autorizată, aveți o problemă de securitate pe mâini. Nu te teme. Decoratorii de clasa Python 3 ți-au luat spatele. Următoarea sintaxă vă va permite (cu definiția adecvată a decoratorului de clasă) să autorizați automat fiecare metodă a clasei țintă:


Categoria superioară autorizată (obiect): def f_1 (* args, secret): def f_2 (* args, secret) "" ""

Tot ce trebuie să faceți este să decorați clasa însăși. Rețineți că decoratorul poate fi inteligent și poate ignora o metodă specială __ metodei __init () sau poate fi configurat să se aplice unui anumit subset, dacă este necesar. Cerul (sau imaginația ta) este limita.

Mai multe exemple

Dacă doriți să continuați cu alte exemple, consultați PythonDecoratorLibrary. 

Ce este un decorator?

Acum că ați văzut câteva exemple în acțiune, este timpul să dezvăluiți magia. Definitia formala este aceea ca un decorator este un callabil care accepta o persoana callabila (tinta) si returneaza un callabil (decorat) care accepta aceleasi argumente ca si tinta initiala. 

Uau! asta este o mulțime de cuvinte îngrămădite unul pe altul, de neînțeles. În primul rând, ce este o persoană calamită? Un apel callabil este doar un obiect Python care are a __apel__() metodă. Acestea sunt de obicei funcții, metode și clase, dar puteți implementa a __apel__() pe una din clasele dvs. și apoi instanțele dvs. de clasă vor deveni și ele callabile. Pentru a verifica dacă un obiect Python poate fi sunat, puteți utiliza funcția încorporată () încorporată ():


callable (len) Adevărat sunătoare ('123') Falsă

Rețineți că callable () funcția a fost eliminată din Python 3.0 și adusă înapoi în Python 3.2, așadar dacă, din anumite motive, utilizați Python 3.0 sau 3.1, va trebui să verificați existența __apel__ atribut ca în hasattr (len, '__call__').

Când luați un astfel de decorator și aplicați-l utilizând sintaxa @ la unele persoane care se calculează, originalul care se calculează este înlocuit cu apelantul returnat de la decorator. Acest lucru poate fi un pic dificil de înțeles, așa că haideți să-l ilustram privindu-ne în curajul unor decoratori simpli.

Decoratori de funcții

Un decorator de funcții este un decorator care este folosit pentru a decora o funcție sau o metodă. Să presupunem că dorim să imprimați șirul "Da, funcționează!" de fiecare dată când este apelată o funcție sau o metodă decorată înainte de a invoca funcția originală. Aici este un mod non-decorator pentru ao realiza. Aici este funcția foo () care imprimă "foo () aici":

def foo (): print 'foo () aici' foo () Ieșire: foo () aici 

Iată modul urât pentru a obține rezultatul dorit:

original_foo = foo def decor_foo (): tipăriți "Da, funcționează!" original_foo () foo = decorated_foo foo () Ieșire: Da, funcționează! foo () aici

Există mai multe probleme cu această abordare:

  • E o mulțime de lucruri.
  • Poluezi spațiul de nume cu nume intermediare, cum ar fi original_foo () și decorated_foo ().
  • Trebuie să o repetați pentru orice altă funcție pe care doriți să o decorați cu aceeași capacitate.

Un decorator care realizează același rezultat și este, de asemenea, reutilizabil și compozabil, arată astfel:

def yeah_it_works (f): Def decorat (* args, ** kwargs): print 'Da, functioneaza' return f (* args, ** kwargs)

Rețineți că yeah_it_works () este o funcție (prin urmare, callabilă) care acceptă un ** f ** apelat ca argument, și returnează un apel care poate fi sunat (funcția imbricată ** decorată **) care acceptă orice număr și tipuri de argumente.

Acum o putem aplica oricarei functii:


() print 'f1 () print' f1 () aici '@yeah_it_works def f2 () Da, funcționează f1 () aici Da, funcționează f2 () aici Da, funcționează f3 () aici

Cum functioneazã? Originalul f1, F2 și f3 funcțiile au fost înlocuite cu funcția imbricată decorată, returnată yeah_it_works. Pentru fiecare funcție, capturată f callabil este funcția inițială ( f1F2 sau f3), astfel încât funcția decorată este diferită și are dreptate, care este imprimat "Da, funcționează!" și apoi invocați funcția inițială f.

Decoratori de clasă

Decoratorii de clasă operează la un nivel superior și decorează o clasă întreagă. Efectul lor are loc la ora de definire a clasei. Puteți să le folosiți pentru a adăuga sau a elimina metode de orice clasă decorată sau chiar pentru a aplica decoratori de funcții la un întreg set de metode. 

Să presupunem că vrem să urmărim toate excepțiile ridicate dintr-o anumită clasă într-un atribut de clasă. Să presupunem că deja avem un decorator de funcții numit track_exceptions_decorator care îndeplinește această funcție. Fără un decorator de clasă, îl puteți aplica manual fiecărei metode sau pentru a recurge la metaclase. De exemplu:


clasa A (obiect): @track_exceptions_decorator def f1 (): ... @track_exceptions_decorator def f2 (): ... @track_exceptions_decorator def f100 (): ... 

Un decorator de clasă care realizează același rezultat este:


Definiți toate atributele callabile ale clasei callable_attributes = k: v pentru k, v în elemente cl .__ dict __. () dacă este apelat (v) # Decorați fiecare atribut callabil de la clasa de intrare pentru nume , func in callable_attributes.items (): decorate = track_exceptions_decorator (func) setattr (cls, nume, decorat) return cls @track_exceptions clasa A: def f1 (self): print (' '2')

Concluzie

Python este bine cunoscut pentru flexibilitatea sa. Decoratorii o duc la nivelul următor. Puteți împacheta preocupările transversale în decoratoarele reutilizabile și le puteți aplica funcțiilor, metodelor și claselor întregi. Recomand foarte mult ca fiecare dezvoltator serios Python să se familiarizeze cu decoratorii și să profite din plin de beneficiile lor.

Cod