Decoratorii sunt una dintre cele mai frumoase caracteristici ale programului Python, dar pentru începătorul programatorului Python, ele pot părea magice. Scopul acestui articol este de a înțelege, în profunzime, mecanismul din spatele decoratorilor Python.
Iată ce veți învăța:
În cazul în care nu ați văzut încă unul (sau poate că nu ați știut că aveți de-a face cu unul), decoratorii arată astfel:
@decorator def function_to_decorate (): treceți
De obicei, le întâlniți peste definiția unei funcții și sunt prefixate de acestea @
. Decoratorii sunt deosebit de buni pentru păstrarea codului DRY (Nu te repeta), și fac acest lucru în timp ce îmbunătățesc, de asemenea, lizibilitatea codului.
Încă fuzzy? Nu fi, deoarece decoratorii sunt doar funcții Python. Asta e corect! Știți deja cum să creați unul. De fapt, principiul fundamental din spatele decoratorilor este componența funcțiilor. Să luăm un exemplu:
def x_plus_2 (x): retur x + 2 print (x_plus_2 (2)) # 2 + 2 == 4 def x_squared (x): return x * x print (x_squared compune cele două funcții pentru x = 2 print (x_squared (x_plus_2 (2))) # (2 + 2) ^ 2 == 16 imprimare (x_squared (x_plus_2 (3))) print (x_squared (x_plus_2 (4))) # (4 + 2) ^ 2 == 36
Dacă am vrea să creăm o altă funcție, x_plus_2_squared
? Încercarea de a compune funcțiile ar fi inutilă:
x_squared (x_plus_2) # TypeError: tipuri de operand neacceptate pentru *: 'function' si 'function'
Nu puteți să compuneți funcții în acest fel deoarece ambele funcții iau numere ca argumente. Cu toate acestea, aceasta va funcționa:
# Să creați acum o compoziție adecvată a funcției fără a aplica efectiv funcția x_plus_2_squared = lambda x: x_squared (x_plus_2 (x)) print (x_plus_2_squared (2)) # (2 + 2) ^ 2 == 16 imprimare (x_plus_2_squared (3)) # (3 + 2) ^ 2 == 25 imprimare (x_plus_2_squared (4)) # (4 + 2) ^ 2 == 36
Să redefinăm cum x_squared
lucrări. Dacă vrem x_squared
pentru a putea fi compilabil în mod implicit, ar trebui:
Vom numi versiunea compozibilă din x_squared
pur şi simplu pătrat
.
(2)) (2 + 2) ^ 2 == 16 tipărire (pătrat (x_plus_2) (3)) # (3 + 2) ^ 2 == 25 imprimare (pătrat (x_plus_2) (4)) # (4 + 2) ^ 2 == 36
Acum că am definit-o pătrat
funcționează într-un mod care îl face comprehensibil, îl putem folosi cu orice altă funcție. Aici sunt cateva exemple:
(x + 2) (x + 2) (x + 2) (x + 2) ) # (2 * 2) ^ 2 == 16
Putem spune că pătrat
decorați funcțiile x_plus_2
, x_plus_3
, și x_times_2
. Suntem foarte apropiați de realizarea notației standard de decorator. Verificați acest lucru:
x_plus_2 = pătrat (x_plus_2) # Am decorat x_plus_2 cu imprimare în formă pătrată (x_plus_2 (2)) # x_plus_2 returnează acum rezultatul împărțit în pătrat: (2 + 2) ^ 2
Asta e! x_plus_2
este o funcție corectă decorată de Python. Aici este locul unde @
notație intră în vigoare:
def x_plus_2 (x): retur x + 2 x_plus_2 = pătrat (x_plus_2) # ^ Acest lucru este complet echivalent cu: @squared def x_plus_2 (x): return x + 2
De fapt, @
notația este o formă de zahăr sintactic. Să încercăm:
(x) x (x) x (x): retur 3 x x print (x_times_3 (2)) # (3 * 2) ^ 2 = 36. # 3 * x) * (3 * x) @squared def x_minus_1 (x): întoarcere x - 1 imprimare (x_minus_1 (3)) # (3-1) ^ 2 = 4
Dacă pătrat
este primul decorator pe care l-ai scris vreodată, dă-ți un pat mare pe spate. Ați înțeles unul dintre cele mai complexe concepte din Python. Pe parcurs, ați învățat o altă caracteristică fundamentală a limbajelor de programare funcțională: funcție de compoziție.
Un decorator este o funcție care ia o funcție ca argument și returnează o altă funcție. Acestea fiind spuse, modelul generic pentru definirea unui decorator este:
Def decorator (function_to_decorate): # ... retur decorate
În cazul în care nu știați, puteți defini funcții în interiorul funcțiilor. În majoritatea cazurilor, decorated_function
va fi definit în interior decorator
.
def decorator (function_to_decorate): def decor_function (* args, ** kwargs): # ... Din moment ce decoreaza 'function_to_decorate', ar trebui sa il folosim undeva in interiorul aici return_function_to_decorate
Să examinăm un exemplu mai practic:
importul pytz de la datetime datetime de import def to_utc (function_to_decorate): def decor_function (): # Obtinerea rezultatului function_to_decorate si transformarea rezultatului in returnul UTC function_to_decorate () astimezone (pytz.utc) return_function_to_utc def package_pickup_time (): " "Acest lucru poate veni dintr-o bază de date sau dintr-un API" "" tz = pytz.timezone ('US / Pacific') returnați tz.localize (datetime (2017, 8, 2, 12, 30, 0, 0) to_utc def package_delivery_time (): "" Aceasta poate veni dintr-o bază de date sau dintr-un API "" "tz = pytz.timezone ('US / Eastern') returnați tz.localize (datetime (2017, 8, 2, 12, 30 , 0, 0)) # Ce coincidență, în același timp cu fusul orar diferit! print ("PICKUP:", pachet_pickup_time ()) # '2017-08-02 19: 30: 00 + 00: 00' print (" 00 + 00: 00'
Dulce! Acum puteți fi siguri că tot ce se află în aplicația dvs. este standardizat pentru fusul orar UTC.
Un alt caz foarte popular și clasic pentru decoratori este caching rezultatul unei funcții:
import time def cached (function_to_decorate): _cache = # Unde păstrăm rezultatele def decor_function (* args): start_time = time.time () print ('_ cache:', _cache) ] = function_to_decorate (* args) # Efectuați calculul și păstrați-l în memoria cache ('Calculate time:% ss'% rotund (time.time () - start_time, 2) return_cache [return] decor_function @cached def complex_computation (x, y): print ('Prelucrare ...') time.sleep (2) return x + y print (complex_computation (1, 2)) # 3, , SKIP efectuează operațiunea de tipărire scumpă (complex_computation (4, 5)) # 9, Efectuarea operației scumpe de tipărire (complex_computation (4, 5)) # , SKIP efectuând operațiunea costisitoare
Dacă te uiți la codul prea puțin, poți obiecta. Decoratorul nu este reutilizabil! Dacă decorăm o altă funcție (de ex another_complex_computation
) și apelați-l cu aceiași parametri, atunci vom obține rezultatele cache din complex_computation function
. Acest lucru nu se va întâmpla. Decoratorul este reutilizabil și de aici:
@cached def alt_complex_computation (x, y): print ('Prelucrare ...') time.sleep (2) retur x * y print (another_complex_computation (1, 2) )) # 2, SKIP efectuând operațiunea de imprimare costisitoare (another_complex_computation (1, 2)) # 2, SKIP efectuând operațiunea costisitoare
în cache
funcția este numită o singură dată pentru fiecare funcție pe care o decorează, deci una diferită _cache
variabila este instanțiată de fiecare dată și trăiește în acest context. Să încercăm acest lucru:
print (complex_computation (10, 20)) # -> 30 imprimare (alt_complex_computare (10, 20)) # -> 200
Decoratorul pe care tocmai l-am codificat, după cum probabil ați observat, este foarte util. Este atât de util ca o versiune mai complexă și mai robustă să existe deja în standard functools
modul. Este numit lru_cache
. LRU este abrevierea lui Destul de recent utilizate, o strategie de caching.
de la functools import lru_cache @lru_cache () def complex_computation (x, y): print ('Prelucrare ...') time.sleep (2) return x + y print (complex_computation (1, 1, 2)) # 3 print (complex_computation (2, 3)) # Prelucrare ... 5 print (complex_computation (1, 2)) #
Una dintre utilizările mele preferate de decoratori este în cadrul web al Flask. Este atât de îngrijorat că acest fragment de cod este primul lucru pe care îl vedeți pe site-ul web al site-ului Flask. Iată fragmentul:
din importul flaconului Flask app = Flask (__ name__) @ app.route ("/") def hello (): reveniți "Hello World!" dacă __name__ == "__main__": app.run ()
app.route
decoratorul atribuie funcția Salut
ca gestionar al cererii pentru ruta "/"
. Simplitatea este uimitoare.
O altă utilizare îngrijită a decoratorilor este în interiorul Django. De obicei, aplicațiile web au două tipuri de pagini:
Dacă încercați să vizualizați o pagină de tipul cel din urmă, de obicei, veți fi redirecționat către o pagină de conectare. Iată cum să implementăm acest lucru în Django:
de la importul django.http HttpResponse de la importul django.contrib.auth.decorators login_required # Pagini publice def home (request): retur HttpResponse ("Acasă") def landing (cerere): retur HttpResponse ("Aterizare") # Pagini de autentificare defecte @login_required (login_url = '/ login') def: (return) HttpResponse ("Tablou de bord") @login_required (login_url = '/ login') def profil_settings (cerere): retur HttpResponse ("Setarile profilului„)
Observați cât de frumos sunt afișate vederile private login_required
. În timp ce trece prin cod, este foarte clar pentru cititor care pagini solicită utilizatorului să se conecteze și care pagini nu.
Sper că ați avut plăcere să învățați despre decoratori, deoarece reprezintă o caracteristică Python foarte curată. Iată câteva lucruri de reținut:
Nu uitați să verificați ce avem disponibile pentru vânzare și pentru studiul pe Envato Market și nu ezitați să puneți întrebări și să furnizați feedback-ul valoros utilizând feedul de mai jos.
!