In het artikel Deep Dive Into Python Decorators, introduceerde ik het concept van Python-decorateurs, demonstreerde veel coole decorateurs en legde uit hoe ze te gebruiken.
In deze tutorial laat ik je zien hoe je je eigen decorateurs kunt schrijven. Zoals je zult zien, geeft het schrijven van je eigen decorateurs je veel controle en maakt je veel mogelijkheden. Zonder decorateurs zou die functionaliteit veel foutgevoelige en repetitieve boilerplate vereisen die uw code verstikt of volledig externe mechanismen zoals het genereren van codes.
Een korte samenvatting als je niets over decorateurs weet. Een binnenhuisarchitect is een opvraagbare (functie, methode, klasse of object met een telefoontje() methode) die een opvraagbare waarde als invoer accepteert en een opvraagbare waarde als uitvoer retourneert. Doorgaans doet de geretourneerde opvraag iets vóór en / of na het aanroepen van de invoer die kan worden gecalld. U past de decorateur toe door @ te gebruiken
Laten we beginnen met een 'Hallo wereld!' decorateur. Deze inrichter vervangt alle aangeklede versies van de callable door een functie die alleen 'Hello World!'.
python def hello_world (f): def decorated (* args, ** kwargs): print 'Hello World!' terug versierd
Dat is het. Laten we het in actie zien en vervolgens de verschillende stukken uitleggen en hoe het werkt. Stel dat we de volgende functie hebben die twee nummers accepteert en hun product afdrukt:
python def vermenigvuldigen (x, y): print x * y
Als je een beroep doet, krijg je wat je verwacht:
vermenigvuldigen (6, 7) 42
Laten we het versieren met onze Hallo Wereld decorateur door annotatie van de vermenigvuldigen functie met @Hallo Wereld
.
python @hello_world def vermenigvuldigen (x, y): print x * y
Nu, wanneer u belt vermenigvuldigen met alle argumenten (inclusief verkeerde gegevenstypes of verkeerd aantal argumenten), is het resultaat altijd 'Hallo wereld!' gedrukt.
"Python Multiply (6, 7) Hallo wereld!
vermenigvuldigen () Hallo wereld!
vermenigvuldigen ('zzz') Hallo wereld! "
OK. Hoe werkt het? De oorspronkelijke vermenigvuldigingsfunctie werd volledig vervangen door de geneste gedecoreerde functie binnen de Hallo Wereld decorateur. Als we de structuur van de Hallo Wereld decorateur dan zul je zien dat het de invoer accepteert die kan worden ingewisseld f (die niet wordt gebruikt in deze eenvoudige decorateur), definieert het een geneste functie genaamd versierd die elke combinatie van argumenten en zoekwoordargumenten accepteert (def decorated (* args, ** kwargs)
) en tenslotte retourneert het de versierd functie.
Er is geen verschil tussen het schrijven van een functie en een methode-decorateur. De definitie van de decorateur is hetzelfde. De invoerbare opvraagbare waarde zal een reguliere functie of een gebonden methode zijn.
Laten we dat controleren. Hier is een decorateur die alleen de invoer die kan worden gecallteerd en het type afdrukt voordat deze wordt aangeroepen. Dit is typisch voor een decorateur om wat actie uit te voeren en door te gaan met het oproepen van de originele opvraagbare actie.
python def print_callable (f): def decorated (* args, ** kwargs): print f, type (f) return f (* args, ** kwargs) return ingericht
Let op de laatste regel die de invoer opvraagbaar op een generieke manier oproept en het resultaat retourneert. Deze binnenhuisarchitect is niet-opdringerig in die zin dat je elke functie of methode in een werkende toepassing kunt verfraaien, en de applicatie zal blijven werken omdat de versierde functie het origineel oproept en slechts een beetje neveneffect heeft voordat.
Laten we het in actie zien. Ik versier onze beide vermenigvuldigingsfuncties en een methode.
"python @print_callable def multiply (x, y): print x * y
klasse A (object): @print_callable def foo (self): print hier 'foo ()'
Wanneer we de functie en de methode aanroepen, wordt de opvraagbaarheid afgedrukt en voeren ze vervolgens hun oorspronkelijke taak uit:
"Python vermenigvuldigen (6, 7)
A (). Foo ()
Decorateurs kunnen ook argumenten aanvoeren. Deze mogelijkheid om de werking van een binnenhuisarchitect in te stellen is zeer krachtig en stelt u in staat in meerdere contexten dezelfde decorateur te gebruiken.
Stel dat je code veel te snel is en je baas vraagt je om het een beetje te vertragen omdat je de andere teamleden er slecht uit laat zien. Laten we een decorateur schrijven die meet hoe lang een functie actief is en of deze in minder dan een bepaald aantal seconden wordt uitgevoerd t, het zal wachten tot t seconden verlopen en dan terugkeren.
Wat nu anders is, is dat de binnenhuisarchitect zelf een ruzie maakt t dat bepaalt de minimale looptijd en verschillende functies kunnen worden ingericht met verschillende minimale looptijden. Ook zul je merken dat er bij het introduceren van decoratorargumenten twee niveaus van nesten zijn vereist:
"python import tijd
def minimum_runtime (t): def decorated (f): def wrapper (args, ** kwargs): start = time.time () result = f (args, ** kwargs) runtime = time.time () - start als runtime < t: time.sleep(t - runtime) return result return wrapper return decorated"
Laten we het uitpakken. De binnenhuisarchitect zelf - de functie minimum_runtime neemt een argument t, Dit is de minimale looptijd voor de versierde opvraagbare telefoon. De invoer kan worden gecalld f werd "naar beneden gedrukt" naar de geneste versierd functie, en de argumenten voor opvraagbare invoer zijn "naar beneden gedrukt" naar nog een andere geneste functie wikkel.
De eigenlijke logica vindt plaats binnen de wikkel functie. De starttijd wordt vastgelegd, de oorspronkelijke opvraagbare f wordt aangeroepen met zijn argumenten en het resultaat wordt opgeslagen. Vervolgens wordt de runtime gecontroleerd en als deze minder is dan het minimum t dan slaapt het voor de rest van de tijd en keert dan terug.
Om het te testen, maak ik een aantal functies die vermenigvuldigd worden genoemd en versier ze met verschillende vertragingen.
"python @minimum_runtime (1) def slow_multiply (x, y): vermenigvuldigen (x, y)
@minimum_runtime (3) def slower_multiply (x, y): vermenigvuldigen (x, y) "
Ik bel nu vermenigvuldigen direct evenals de langzamere functies en meet de tijd.
"python import tijd
funcs = [vermenigvuldig, langzaam_veelvoudig, langzamer_veelvoud] voor f in functies: start = time.time () f (6, 7) print f, time.time () - start "
Dit is de uitvoer:
gewoon 42
Zoals u kunt zien, nam de oorspronkelijke vermenigvuldiging bijna geen tijd in beslag, en de langzamere versies werden inderdaad vertraagd volgens de opgegeven minimale looptijd.
Een ander interessant feit is dat de uitgevoerde gedecoreerde functie de verpakking is, wat logisch is als je de definitie van de versiering volgt. Maar dat kan een probleem zijn, vooral als we te maken hebben met stackdecorateurs. De reden is dat veel decorateurs ook hun invoer opvraagbaar inspecteren en de naam, handtekening en argumenten controleren. In de volgende secties wordt dit probleem onderzocht en wordt advies gegeven voor de beste werkwijzen.
U kunt ook objecten als decorateur gebruiken of objecten van uw decorateurs retourneren. De enige vereiste is dat ze een __call __ () methode, zodat ze opvraagbaar zijn. Hier is een voorbeeld voor een op objecten gebaseerde decorateur die telt hoe vaak de doelfunctie wordt aangeroepen:
python-klasse Teller (object): def __init __ (zelf, f): self.f = f self.called = 0 def __call __ (zelf, * args, ** kwargs): self.called + = 1 return self.f (* args, ** kwargs)
Hier is het in actie:
"python @Counter def bbb (): print 'bbb'
bbb () bbb
bbb () bbb
bbb () bbb
print bbb.called 3 "
Dit is meestal een kwestie van persoonlijke voorkeur. Geneste functies en functiesluitingen bieden alle statusbeheer dat objecten bieden. Sommige mensen voelen zich meer thuis bij lessen en objecten.
In de volgende sectie zal ik discussiëren over goed opgevoede binnenhuisarchitecten, en objectgebaseerde decorateurs nemen wat extra werk om zich goed te gedragen.
Decorateurs voor algemene doeleinden kunnen vaak worden gestapeld. Bijvoorbeeld:
python @ decorator_1 @ decorator_2 def foo (): print hier 'foo ()'
Bij het stapelen van decorateurs ontvangt de buitendeur (decorator_1 in dit geval) de opvraagbare retouraanroep van de binnenhuisarchitect (decorator_2). Als decorator_1 op de een of andere manier afhankelijk is van de naam, argumenten of docstring van de oorspronkelijke functie en decorator_2 naïef is geïmplementeerd, zal decorator_2 de juiste informatie niet zien van de oorspronkelijke functie, maar alleen de opvraagbare geretourneerd door decorator_2.
Hier is bijvoorbeeld een binnenhuisarchitect die controleert of de naam van zijn doelfunctie allemaal in kleine letters is:
python def check_lowercase (f): def decorated (* args, ** kwargs): assert f.func_name == f.func_name.lower () f (* args, ** kwargs) return ingericht
Laten we er een functie mee decoreren:
python @check_lowercase def Foo (): print hier 'Foo ()'
Het aanroepen van Foo () resulteert in een bewering:
"plain In [51]: Foo () - AssertionError Traceback (laatste oproep laatste)