I artikeln Deep Dive Into Python Decorators introducerade jag konceptet Python decorators, visade många coola dekoratörer och förklarade hur man använde dem.
I denna handledning visar jag dig hur man skriver egna dekoratörer. Som du ser kan du skriva ut dina egna dekoratörer och ge dig många möjligheter. Utan dekoratörer skulle de här egenskaperna kräva mycket felaktigt och repetitivt boilerplatta som klämmer fast din kod eller helt externa mekanismer som kodgenerering.
En snabb omgång om du inte vet någonting om dekoratörer. En dekoratör är en callable (funktion, metod, klass eller objekt med a ring upp() -metoden) som accepterar en callable som input och returnerar en callable som utgång. Vanligtvis gör det returnerade callable någonting före och / eller efter att anropet har ringts in. Du tillämpar dekoratorn genom att använda @
Låt oss börja med en "Hej värld!" dekoratör. Denna dekoratör kommer helt att ersätta alla dekorerade callable med en funktion som bara skriver ut 'Hello World!'.
python def hello_world (f): def dekorerade (* args, ** kwargs): skriv ut 'Hello World!' återvända inredda
Det är allt. Låt oss se det i aktion och förklara de olika bitarna och hur det fungerar. Antag att vi har följande funktion som accepterar två siffror och skriver ut sin produkt:
python def multiplicera (x, y): skriv ut x * y
Om du åberopar får du vad du förväntar dig:
multiplicera (6, 7) 42
Låt oss dekorera det med vårt Hej världen dekoratör genom att kommentera multiplicera fungera med @Hej världen
.
python @hello_world def multiplicera (x, y): skriv ut x * y
Nu när du ringer multiplicera med några argument (inklusive felaktiga datatyper eller felaktigt antal argument) är resultatet alltid "Hello World!" tryckt.
"Python multiplicera (6, 7) Hello World!
multiplicera () Hello World!
multiplicera ("zzz") Hej världen! "
OK. Hur fungerar det? Den ursprungliga multipliceringsfunktionen ersattes helt av den nestade dekoreringsfunktionen inuti Hej världen dekoratör. Om vi analyserar strukturen hos Hej världen dekoratorn så ser du att den accepterar ingången som kan ringas f (som inte används i denna enkla dekoratör), definierar den en kapslad funktion som heter dekorerad som accepterar en kombination av argument och sökordsargument (def dekorerade (* args, ** kwargs)
), och slutligen returnerar den dekorerad fungera.
Det finns ingen skillnad mellan att skriva en funktion och en metodinstruktör. Dekoreringsdefinitionen kommer att vara densamma. Inmatningsangivelsen är antingen en vanlig funktion eller en bunden metod.
Låt oss verifiera det. Här är en dekoratör som bara skriver ut ingången som kan ringas och skriver innan den påkallas. Detta är mycket typiskt för en dekoratör att utföra en viss åtgärd och fortsätt genom att åberopa det ursprungliga kallbara.
python def print_callable (f): def dekorerad (* args, ** kwargs): skriv ut f, typ (f) returnera f (* args, ** kwargs)
Notera den sista raden som påkallar ingången som kan ringas på ett generiskt sätt och returnerar resultatet. Denna dekoratör är icke-påträngande i den meningen att du kan dekorera någon funktion eller metod i en arbetsapplikation, och ansökan fortsätter att fungera eftersom den inredda funktionen påkallar originalen och bara har en liten bieffekt före.
Låt oss se det i aktion. Jag ska dekorera både vår multiplicera funktion och en metod.
"python @print_callable def multiplicera (x, y): print x * y
klass A (objekt): @print_callable def foo (själv): skriv ut 'foo () here "
När vi kallar funktionen och metoden, skrivs den utskrivbara ut och sedan utför de sin ursprungliga uppgift:
"Python multiplicera (6, 7)
A (). Foo ()
Dekoratörer kan ta argument också. Denna förmåga att konfigurera en decorators funktion är mycket kraftfull och låter dig använda samma dekoratör i många sammanhang.
Antag att din kod är alldeles för snabb, och din chef ber dig att sakta ner det lite för att du gör de andra lagmedlemmarna dåliga. Låt oss skriva en dekoratör som mäter hur länge en funktion körs och om den körs på mindre än ett visst antal sekunder t, det väntar tills t sekunder löper ut och återkommer sedan.
Vad som är annorlunda nu är att dekoratören själv tar ett argument t som bestämmer minsta körtid, och olika funktioner kan dekoreras med olika minsta körtid. Du kommer också märka att när man introducerar dekoratörsargument krävs två nivåer av häckning:
"python importtid
def minimum_runtime (t): def dekorerade (f): def wrapper (args, ** kwargs): start = time.time () resultat = f (args, ** kwargs) runtime = time.time () - starta om körtid < t: time.sleep(t - runtime) return result return wrapper return decorated"
Låt oss packa upp det. Dekoratören själv-funktionen minimum_runtime tar ett argument t, vilket representerar minsta körtid för den dekorerade callable. Inmatningen kan ringas f var "nedtryckt" till det kapslade dekorerad funktionen och de inmatningsbara argumenten "pushed down" till ännu en nestad funktion omslag.
Den faktiska logiken sker inom omslag fungera. Starttiden är inspelad, den ursprungliga kan ringas f åberopas med sina argument, och resultatet lagras. Därefter kontrolleras körtiden, och om den är mindre än minimum t då sover den för resten av tiden och återkommer sedan.
För att testa det skapar jag ett par funktioner som kallar multiplicera och dekorera dem med olika förseningar.
"python @minimum_runtime (1) def slow_multiply (x, y): multiplicera (x, y)
@minimum_runtime (3) def slow_multiply (x, y): multiplicera (x, y) "
Nu ska jag ringa multiplicera direkt liksom de långsammare funktionerna och mäta tiden.
"python importtid
funcs = [multiplicera, slow_multiply, slow_multiply] för f i funcs: start = time.time () f (6, 7) print f, time.time () - starta "
Här är utgången:
vanligt 42
Som du kan se tog den ursprungliga multipliceringen nästan ingen tid, och de långsammare versionerna fördröjdes verkligen enligt den angivna minimiperioden.
Ett annat intressant faktum är att den utförda dekorerade funktionen är omslaget, vilket är meningsfullt om du följer definitionen på dekorerad. Men det kan vara ett problem, speciellt om vi har att göra med stack decorators. Anledningen är att många dekoratörer också inspekterar deras inmatningsbara och kontrollerar namn, signatur och argument. Följande avsnitt kommer att undersöka problemet och ge råd om bästa praxis.
Du kan också använda föremål som dekoratörer eller returnera objekt från dina dekoratörer. Det enda kravet är att de har en __ring upp__() metod, så de kan kallas. Här är ett exempel på en objektbaserad dekoratör som räknar hur många gånger dess målfunktion heter:
python-klass Counter (objekt): def __init __ (själv, f): self.f = f self.called = 0 def __call __ (själv, * args, ** kwargs): self.called + = 1 returnera self.f (* args, ** kwargs)
Här är det i aktion:
"python @Counter def bbb (): skriv ut 'bbb'
bbb () bbb
bbb () bbb
bbb () bbb
skriv ut bbb.called 3 "
Det här är mestadels en fråga om personlig preferens. Nested funktioner och funktion stängningar ger all den statliga ledningen som objekten erbjuder. Vissa människor känner sig mer hemma med klasser och föremål.
I nästa avsnitt kommer jag att diskutera väluppförda dekoratörer, och objektbaserade dekoratörer tar lite extra arbete för att vara välskötta.
Allmänna dekoratörer kan ofta staplas. Till exempel:
python @ decorator_1 @ decorator_2 def foo (): print 'foo () här'
När du ställer in dekoratörer, kommer den yttre dekoratören (decorator_1 i det här fallet) att få det samtal som returneras av inredningsaren (decorator_2). Om decorator_1 beror på något sätt på namnet, är argumenter eller docstring av den ursprungliga funktionen och decorator_2 implementerad naivt, då kommer dekoratorn_2 att se inte se den korrekta informationen från den ursprungliga funktionen, men endast den samtal som returneras av decorator_2.
Till exempel, här är en dekoratör som verifierar sin målsfunktions namn är helt små bokstäver:
python def check_lowercase (f): def dekorerade (* args, ** kwargs): hävda f.func_name == f.func_name.lower () f (* args, ** kwargs)
Låt oss dekorera en funktion med det:
python @check_lowercase def Foo (): print 'Foo () här'
Ringa Foo () resulterar i ett påstående:
"plain In [51]: Foo () - AssertionError Traceback (senaste samtal sist)