I artikkelen Deep Dive Into Python Decorators presenterte jeg begrepet Python decorators, demonstrerte mange kule dekoratører, og forklarte hvordan de skulle brukes.
I denne veiledningen vil jeg vise deg hvordan du skriver dine egne dekoratører. Som du vil se, kan du skaffe deg mye kontroll og lage mange muligheter. Uten dekoratører ville disse evnene kreve mye feilaktig og repeterende boilerplate som clutters koden din eller helt eksterne mekanismer som kodegenerering.
En rask omtale hvis du ikke vet noe om dekoratører. En dekoratør er en callable (funksjon, metode, klasse eller objekt med a anrop() metode) som aksepterer en callable som input og returnerer en callable som utgang. Vanligvis gjør den returnerte callable noe før og / eller etter at innringingen er mulig. Du bruker dekoratøren ved å bruke @
La oss starte med en "Hei verden!" dekoratør. Denne dekoratøren vil helt erstatte alle dekorert callable med en funksjon som bare skriver ut 'Hello World!'.
python def hello_world (f): def dekorert (* args, ** kwargs): skriv ut 'Hello World!' returnere dekorert
Det er det. La oss se det i aksjon og forklare de forskjellige stykkene og hvordan det fungerer. Anta at vi har følgende funksjon som aksepterer to tall og skriver ut deres produkt:
python def multiply (x, y): print x * y
Hvis du påberoper, får du det du forventer:
multiplisere (6, 7) 42
La oss dekorere den med vår Hei Verden dekoratør ved å annotere multiplisere fungere med @Hei Verden
.
python @hello_world def multiply (x, y): print x * y
Nå, når du ringer multiplisere med eventuelle argumenter (inkludert feil datatyper eller feil antall argumenter), er resultatet alltid 'Hello World!' skrevet ut.
"Python multipliserer (6, 7) Hello World!
multiplisere () Hello World!
multiplisere ('zzz') Hei verden! "
OK. Hvordan virker det? Den opprinnelige multipliseringsfunksjonen ble helt erstattet av den nestede dekorerte funksjonen inne i Hei Verden dekoratør. Hvis vi analyserer strukturen til Hei Verden dekorator så ser du at den aksepterer inntastingen som kan kalles f (som ikke er brukt i denne enkle dekoratøren), definerer den en nestet funksjon som kalles dekorert som aksepterer en kombinasjon av argumenter og søkeord argumenter (def dekorert (* args, ** kwargs)
), og til slutt returnerer den dekorert funksjon.
Det er ingen forskjell mellom å skrive en funksjon og en metodeutsmyker. Dekoratørdefinisjonen vil være den samme. Inngangskallet vil enten være en vanlig funksjon eller en bundet metode.
La oss bekrefte det. Her er en dekoratør som bare skriver inn innkallingen som kan ringes og skriver før den påkaller seg. Dette er veldig typisk for en dekoratør å utføre en handling og fortsette ved å påkalle den opprinnelige callable.
python def print_callable (f): def dekorert (* args, ** kwargs): print f, type (f) returnere f (* args, ** kwargs)
Merk den siste linjen som påkaller inntastingen som kan ringes inn generisk, og returnerer resultatet. Denne dekoratøren er ikke-påtrengende i den forstand at du kan dekorere hvilken som helst funksjon eller metode i et arbeidsapplikasjon, og søknaden vil fortsette å fungere fordi den dekorerte funksjonen påkaller originalen og bare har en liten bivirkning før.
La oss se det i aksjon. Jeg skal dekorere både vår multiplikasjonsfunksjon og en metode.
"python @print_callable def multiply (x, y): print x * y
klasse A (objekt): @print_callable def foo (selv): skriv ut 'foo () her "
Når vi kaller funksjonen og metoden, skrives den utringbare ut, og deretter utfører de sin opprinnelige oppgave:
"Python Multiply (6, 7)
A (). Foo ()
Dekoratører kan også ta argumenter. Denne muligheten til å konfigurere driften av en dekoratør er meget kraftig og lar deg bruke den samme dekoratøren i mange sammenhenger.
Anta at koden din er altfor fort, og sjefen din spør deg om å bremse den litt, fordi du gjør de andre lagmedlemmene til å se dårlig ut. La oss skrive en dekoratør som måler hvor lenge en funksjon kjører, og hvis den kjører på mindre enn et visst antall sekunder t, det vil vente til t sekunder utløper og deretter returnere.
Det som er forskjellig nå er at dekoratøren selv tar et argument t som bestemmer minimum kjøretid, og forskjellige funksjoner kan dekoreres med forskjellige minimum kjøretider. Også, vil du legge merke til at når du innfører dekorator argumenter, er to nivåer av nesting kreves:
"python importtid
def minimum_runtime (t): def dekorert (f): def wrapper (args, ** kwargs): start = time.time () resultat = f (args, ** kwargs) runtime = time.time () - start hvis kjøretid < t: time.sleep(t - runtime) return result return wrapper return decorated"
La oss pakke ut det. Dekoratøren selv-funksjonen minimum_runtime tar et argument t, som representerer minimum kjøretid for dekorert callable. Inngangskallet f ble "presset ned" til den nestede dekorert funksjon, og de innringbare argumentene ble "presset ned" til enda en nestet funksjon wrapper.
Den faktiske logikken finner sted inne i wrapper funksjon. Starttiden er registrert, den opprinnelige callable f er påkalt med sine argumenter, og resultatet er lagret. Deretter kontrolleres kjøretiden, og hvis den er mindre enn minimum t så sover den for resten av tiden og returnerer deretter.
For å teste det, vil jeg opprette et par funksjoner som kaller multiplisere og dekorere dem med forskjellige forsinkelser.
"python @minimum_runtime (1) def slow_multiply (x, y): multipliserer (x, y)
@minimum_runtime (3) def slow_multiply (x, y): multiply (x, y) "
Nå skal jeg ringe multiplisere direkte så vel som de langsommere funksjonene og måle tiden.
"python importtid
funcs = [multiply, slow_multiply, tregere_multiply] for f i funcs: start = time.time () f (6, 7) print f, time.time () - start "
Her er utgangen:
vanlig 42
Som du ser, tok den opprinnelige multiplikasjonen nesten ingen tid, og de langsommere versjoner var faktisk forsinket i henhold til den angitte minimumsperioden.
Et annet interessant faktum er at den utførte dekorerte funksjonen er omslaget, noe som gir mening hvis du følger definisjonen av dekorert. Men det kan være et problem, spesielt hvis vi har å gjøre med stakkens dekoratører. Årsaken er at mange dekoratører også inspiserer deres innringbare og kontrollerer navn, signatur og argumenter. Følgende avsnitt vil undersøke dette problemet og gi råd om beste praksis.
Du kan også bruke gjenstander som dekoratører eller returnere gjenstander fra dekoratørene dine. Det eneste kravet er at de har en __anrop__() metode, slik at de kan kalles. Her er et eksempel på en objektbasert dekoratør som teller hvor mange ganger målfunksjonen heter:
python klasse Counter (objekt): def __init __ (selv, f): self.f = f self.called = 0 def __call __ (selv, * args, ** kwargs): self.called + = 1 returner self.f (* args, ** kwargs)
Her er det i aksjon:
"python @Counter def bbb (): print 'bbb'
bbb () bbb
bbb () bbb
bbb () bbb
skriv ut bbb.called 3 "
Dette er for det meste et spørsmål om personlig preferanse. Nestede funksjoner og funksjonstenger gir all statlig ledelse som objekter tilbyr. Noen føler seg mer hjemme med klasser og gjenstander.
I neste avsnitt skal jeg diskutere veloppdragen dekoratører, og objektbaserte dekoratører tar litt ekstra arbeid for å være friskopptatt.
Generelle dekoratører kan ofte stables. For eksempel:
python @ decorator_1 @ decorator_2 def foo (): print 'foo () her'
Ved stablering av dekoratører, vil den ytre dekoratøren (decorator_1 i dette tilfellet) motta det kallbare returneres av den innvendige dekoratøren (decorator_2). Hvis decorator_1 avhenger på en eller annen måte på navnet, blir argumenter eller doktrering av den opprinnelige funksjonen og decorator_2 implementert naivt, så vil decorator_2 ikke se den riktige informasjonen fra den opprinnelige funksjonen, men bare den ringbare returnerte av decorator_2.
For eksempel, her er en dekoratør som bekrefter at målfunksjonens navn er helt små:
python def check_lowercase (f): def dekorert (* args, ** kwargs): assert f.func_name == f.func_name.lower () f (* args, ** kwargs) returnert dekorert
La oss dekorere en funksjon med det:
python @check_lowercase def Foo (): print 'Foo () her'
Ringe Foo () resulterer i et påstand:
"plain In [51]: Foo () - AssertionError Traceback (siste samtalen sist)