Tørk Python-koden med dekoratører

Dekoratører er en av de fineste funksjonene i Python, men for den nybegynnere Python-programmereren, kan de virke som magi. Formålet med denne artikkelen er å forstå, i dybden, mekanismen bak Python-dekoratørene.

Her er hva du skal lære:

  • Hva er Python-dekoratører og hva de er gode for
  • hvordan du definerer våre egne dekoratører
  • eksempler på virkelige dekoratører og hvordan de fungerer
  • hvordan å skrive bedre kode ved hjelp av dekoratører

Introduksjon

Hvis du ikke har sett en enda (eller kanskje du ikke visste at du hadde å gjøre med en), ser dekoratører slik ut:

@decorator def funksjon_to_decorate (): passere

Du støter vanligvis på dem over definisjonen av en funksjon, og de er prefixed av @. Dekoratører er spesielt gode for å holde koden din Tørk (Gjenta ikke selv), og de gjør det samtidig som du forbedrer lesbarheten av koden din.

Fortsatt uklar? Ikke vær, siden dekoratører bare er Python-funksjoner. Det er riktig! Du vet allerede hvordan du lager en. Faktisk er det grunnleggende prinsippet bak dekoratører funksjonssammensetning. La oss ta et eksempel:

def x_plus_2 (x): return x + 2 print (x_plus_2 (2)) # 2 + 2 == 4 def x_squared (x): retur x * x print (x_squared (3)) # 3 ^ 2 == 9 # La oss komponere de to funksjonene for x = 2 print (x_squared (x_plus_2 (2))) # (2 + 2) ^ 2 == 16 utskrift (x_squared (x_plus_2 (3))) # (3 + 2) ^ 2 == 25 skriv ut (x_squared (x_plus_2 (4))) # (4 + 2) ^ 2 == 36

Hva om vi ønsket å skape en annen funksjon, x_plus_2_squared? Å prøve å komponere funksjonene ville være ubrukelig:

x_squared (x_plus_2) # TypeError: Ikke-støttet operand type (r) for *: 'funksjon' og 'funksjon'

Du kan ikke komponere funksjoner på denne måten fordi begge funksjonene tar tall som argumenter. Dette vil imidlertid fungere:

# La oss nå lage en riktig funksjonssammensetning uten å faktisk bruke funksjonen x_plus_2_squared = lambda x: x_squared (x_plus_2 (x)) print (x_plus_2_squared (2)) # (2 + 2) ^ 2 == 16 utskrift (x_plus_2_squared (3)) # (3 + 2) ^ 2 == 25 print (x_plus_2_squared (4)) # (4 + 2) ^ 2 == 36

La oss omdefinere hvordan x_squared virker. Hvis vi vil x_squared For å være kompositt som standard bør den:

  1. Godta en funksjon som et argument
  2. Returner en annen funksjon

Vi vil navngi den kompositte versjonen av x_squared ganske enkelt squared.

def kvadrert (func): retur lambda x: func (x) * func (x) utskrift (kvadrert (x_plus_2) (2)) # (2 + 2) ^ 2 == 16 utskrift (kvadrat (x_plus_2) (3)) # (3 + 2) ^ 2 == 25 utskrift (kvadrat (x_plus_2) (4)) # (4 + 2) ^ 2 == 36

Nå som vi har definert squared Fungerer på en måte som gjør den komposibel, kan vi bruke den med en hvilken som helst annen funksjon. Her er noen eksempler:

def x_plus_3 (x): return x + 3 def x_times_2 (x): return x * 2 print (kvadrert (x_plus_3) (2)) # (2 + 3) ^ 2 == 25 print (kvadratisk (x_times_2) ) # (2 * 2) ^ 2 == 16

Vi kan si det squared dekorerer funksjonene x_plus_2x_plus_3, og x_times_2. Vi er svært nær å oppnå standard dekoratør notasjon. Sjekk ut dette:

x_plus_2 = kvadrert (x_plus_2) # Vi dekorerte x_plus_2 med kvadrert utskrift (x_plus_2 (2)) # x_plus_2 returnerer nå det dekorerte kvadratresultatet: (2 + 2) ^ 2 

Det er det! x_plus_2 er en riktig Python dekorert funksjon. Her er hvor @ notat kommer på plass:

def x_plus_2 (x): return x + 2 x_plus_2 = squared (x_plus_2) # ^ Dette er helt ekvivalent med: @squared def x_plus_2 (x): return x + 2

Faktisk er det @ notasjon er en form for syntaktisk sukker. La oss prøve det ut:

@squared def x_times_3 (x): return 3 * x print (x_times_3 (2)) # (3 * 2) ^ 2 = 36. # Det kan være litt forvirrende, men ved å dekorere det med kvadrat, ble x_times_3 faktisk 3 * x) * (3 * x) @squared def x_minus_1 (x): return x - 1 print (x_minus_1 (3)) # (3-1) ^ 2 = 4

Hvis squared er den første dekoratøren du noen gang har skrevet, gi deg en stor klapp på baksiden. Du har tatt et av de mest komplekse konseptene i Python. Underveis lærte du en annen grunnleggende funksjon i funksjonelle programmeringsspråk: funksjonssammensetning.

Bygg din egen dekorator

En dekoratør er en funksjon som tar en funksjon som et argument og gir en annen funksjon. Når det er sagt, er generisk mal for å definere en dekoratør:

def decorator (function_to_decorate): # ... return decorated_function

I tilfelle du ikke visste, kan du definere funksjoner i funksjoner. I de fleste tilfeller er det decorated_function vil bli definert inne dekoratør.

def decorator (function_to_decorate): def decorated_function (* args, ** kwargs): # ... Siden vi dekorere 'function_to_decorate', bør vi bruke det et sted inne her returnere decorated_function

La oss se på et mer praktisk eksempel:

importere pytz fra datetime import datetime def to_utc (function_to_decorate): def decorated_function (): # Få resultatet av function_to_decorate og transform resultatet til UTC return function_to_decorate (). astimezone (pytz.utc) returnered_function @to_utc def package_pickup_time (): " "" Dette kan komme fra en database eller fra en API "" "tz = pytz.timezone ('USA / Pacific') returnerer tz.localize (datetime (2017, 8, 2, 12, 30, 0, 0)) @ to_utc def package_delivery_time (): "" "Dette kan komme fra en database eller fra en API" "" tz = pytz.timezone ('US / Eastern') retur tz.localize (datetime (2017, 8, 2, 12, 30 , 0, 0)) # Hvilket tilfeldig tilfeldighet, samme tidssone! print ("PICKUP:", package_pickup_time ()) # '2017-08-02 19: 30: 00 + 00: 00' print ("LEVERING:", package_delivery_time ()) # '2017-08-02 16:30: 00 + 00: 00'

Søt! Nå kan du være sikker på at alt i appen din er standardisert for UTC-tidssonen.

Et praktisk eksempel

En annen veldig populær og klassisk brukskasse for dekoratører er caching resultatet av en funksjon:

importtid def cached (function_to_decorate): _cache =  # Hvor vi holder resultatene def decorated_function (* args): start_time = time.time () print ('_ cache:', _cache) hvis args ikke er i _cache: _cache [args ] = function_to_decorate (* args) # Utfør beregningen og lagre den i hurtigutskrift ('Beregn tid:% ss'% runde (time.time () - start_time, 2)) returner _cache [args] returned_function @cached def complex_computation (x, y): print ('Behandling ...') time.sleep (2) returnere x + y print (complex_computation (1, 2)) # 3, Utføre den dyre operasjonsutskriften (complex_computation (1, 2)) # 3 , SKIP utfører den dyre operasjonsutskriften (complex_computation (4, 5)) # 9, Utfører den dyre operasjonsutskriften (complex_computation (4, 5)) # 9, SKIP utfører den dyre operasjonsutskriften (complex_computation (1, 2)) # 3 , SKIP utfører den dyre operasjonen

Hvis du ser på koden, kan du motsette seg. Dekoratøren er ikke gjenbrukbar! Hvis vi dekorerer en annen funksjon (si another_complex_computation) og kalle det med de samme parametrene, så får vi de cached resultatene fra complex_computation funksjon. Dette skjer ikke. Dekoratøren er gjenbrukbar, og her er hvorfor:

@cached def another_complex_computation (x, y): print ('Behandler ...') time.sleep (2) returner x * y print (another_complex_computation (1, 2)) # 2, Utfører den dyre operasjonsutskrift (another_complex_computation (1, 2 )) # 2, SKIP utfører den dyre operasjonsutskriften (another_complex_computation (1, 2)) # 2, SKIP utfører den dyre operasjonen

De bufret funksjon kalles en gang for hver funksjon den dekorerer, så en annen _cache variabel er instantiated hver gang og lever i den sammenhengen. La oss teste dette ut:

skriv ut (complex_computation (10, 20)) # -> 30 print (another_complex_computation (10, 20)) -> 200

Dekoratører i naturen

Dekoratøren vi nettopp kodet, som du kanskje har lagt merke til, er veldig nyttig. Det er så nyttig at en mer kompleks og robust versjon allerede finnes i standarden functools modul. Den er oppkalt lru_cache. LRU er forkortelsen til Minst nylig brukt, en caching strategi. 

fra functools importere lru_cache @lru_cache () def complex_computation (x, y): print ('Behandling ...') time.sleep (2) returnere x + y print (complex_computation (1, 2)) # Prosessering ... 3 utskrift (complex_computation 1, 2)) # 3 print (complex_computation (2, 3)) # Behandling ... 5 utskrift (complex_computation (1, 2)) # 3 print (complex_computation (2, 3)) # 5

En av mine favorittbrukere av dekoratører er i rammen for Flask-web. Det er så pent at dette kodestykket er det første du ser på Flaskens nettsted. Her er koden:

fra kolbe import Flask app = Flask (__ name__) @ app.route ("/") def hei (): return "Hello World!" hvis __name__ == "__main__": app.run ()

De app.route dekoratør tilordner funksjonen Hallo som forespørselen håndterer for ruten "/". Enkelheten er fantastisk. 

En annen fin bruk av dekoratører er inne i Django. Vanligvis har webprogrammer to typer sider: 

  1. sider du kan vise uten å være autentisert (forside, destinasjonsside, blogginnlegg, innlogging, register)
  2. sider du må godkjenne for å vise (profilinnstillinger, innboks, oversikt)

Hvis du prøver å vise en side av sistnevnte type, blir du vanligvis omdirigert til en påloggingsside. Slik implementerer du det i Django:

fra django.http importere HttpResponse fra django.contrib.auth.decorators import login_required # Offentlige sider def home (request): return HttpResponse ("Hjem") def landing (forespørsel): return HttpResponse ("Landing") # Autentiserte sider @login_required (login_url = '/ login') def dashboard (forespørsel): return HttpResponse ("dashbord") @login_required (login_url = '/ login') def profile_settings (request): returner HttpResponse ("Profilinnstillinger")

Vær oppmerksom på hvor pent de private visningene er merket med innlogging kreves. Mens du går gjennom koden, er det veldig klart for leseren hvilke sider krever at brukeren logger på og hvilke sider som ikke gjør det.

konklusjoner

Jeg håper du hadde det gøy å lære om dekoratører fordi de representerer en veldig fin Python-funksjon. Her er noen ting å huske:

  • Korrekt bruk og design av dekoratører kan gjøre koden din bedre, renere og vakkerere.
  • Bruke dekoratører kan hjelpe deg med å tørke opp din kode-flytte identisk kode fra innvendige funksjoner til dekoratører.
  • Når du bruker dekoratører mer, finner du bedre, mer komplekse måter å bruke dem på.

Husk å sjekke ut hva vi har tilgjengelig for salg og for studier på Envato Market, og ikke nøl med å stille spørsmål og gi din verdifulle tilbakemelding ved å bruke feedet under.

!