Python-decorateurs zijn een van mijn favoriete Python-functies. Ze zijn de meest gebruikersvriendelijke * en * ontwikkelaarvriendelijke implementatie van aspectgeoriënteerde programmering die ik in elke programmeertaal heb gezien.
Met een decorateur kunt u de logica van een functie of methode vergroten, wijzigen of volledig vervangen. Deze droge beschrijving doet de decorateurs niet recht. Zodra u ze gaat gebruiken, ontdekt u een heel universum van nette applicaties die helpen uw code strak en schoon te houden en belangrijke 'administratieve' taken uit de hoofdstroom van uw code en een decorateur te verwijderen.
Voordat we ingaan op enkele coole voorbeelden, als je de oorsprong van decorateurs een beetje meer wilt verkennen, verschenen functioneel decorateurs als eerste in Python 2.4. Zie PEP-0318 voor een interessante discussie over de geschiedenis, de reden en de keuze van de naam 'decorateur'. Klasse decorateurs verscheen voor het eerst in Python 3.0. Zie PEP-3129, wat vrij kort is en voortbouwt op alle concepten en ideeën van functie-decorateurs.
Er zijn zoveel voorbeelden dat ik moeilijk kan kiezen. Mijn doel hier is om je open te stellen voor de mogelijkheden en je te laten kennismaken met super handige functies die je onmiddellijk aan je code kunt toevoegen door je functies letterlijk te annoteren met een one-liner.
De klassieke voorbeelden zijn de ingebouwde @staticmethod en @classmethod decorateurs. Deze decorateurs veranderen een klassemethode overeenkomstig een statische methode (er wordt geen zelf eerste argument gegeven) of een klassemethode (eerste argument is de klasse en niet de instantie).
klasse A (object): @classmethod def foo (cls): print cls .__ name__ @staticmethod def bar (): print 'Ik heb geen nut voor de instantie of klasse' A.foo () A.bar ()
Output:
A Ik heb geen gebruik van de instantie of klasse
Statische en klassemethoden zijn handig als u geen exemplaar bij de hand hebt. Ze worden veel gebruikt en het was erg lastig om ze toe te passen zonder de syntax van de decorateur.
De decormaker @memoize onthoudt het resultaat van de eerste aanroep van een functie voor een bepaalde set parameters en slaat deze op in de cache. Latere aanroepen met dezelfde parameters retourneren het resultaat in de cache.
Dit zou een enorme prestatieversterker kunnen zijn voor functies die dure verwerking vergen (bijvoorbeeld het bereiken van een database op afstand of het aanroepen van meerdere REST API's) en vaak worden genoemd met dezelfde parameters.
@memoize def fetch_data (items): "" "Doe hier serieus werk" "" result = [fetch_item_data (i) voor i in items] retourresultaat
Wat dacht je van een paar decorateurs die @precondition en @postcondition heetten om het invoerargument en het resultaat te valideren? Overweeg de volgende eenvoudige functie:
def add_small ints (a, b): "" "Voeg twee ints toe waarvan de som nog steeds een int is" "" return a + b
Als iemand het met grote getallen of longs of zelfs snaren noemt, zal het stilletjes slagen, maar het zal het contract schenden dat het resultaat een int moet zijn. Als iemand het met niet-overeenkomende gegevenstypen oproept, krijgt u een generieke runtime-fout. U zou de volgende code aan de functie kunnen toevoegen:
def add_small ints (a, b): "" "Voeg twee ints toe in wiens sum nog steeds een int" "" assert (isinstance (a, int), 'a must be an int') assert (isinstance (a, int ), 'b moet een int zijn') resultaat = a + b beweren (isinstance (resultaat, int), 'de argumenten zijn te groot, som is geen int') retourresultaat
Onze mooie eenregel add_small_ints ()
functie werd een smerig moeras met lelijke beweringen. In een echte functie kan het heel moeilijk zijn om in een oogopslag te zien wat het eigenlijk doet. Met decorateurs kunnen de voorwaarden voor en na het verplaatsen van de hoofdtekst van de functie verdwijnen:
@precondition (isinstance (a, int), 'a must be an int') @reconditionering (isinstance (b, int), 'b moet een int zijn') @postcondition (isinstance (retval, int), 'de argumenten zijn te groot. som is geen int ') def add_small ints (a, b): "" "Voeg twee ints toe waarvan de som nog steeds een int is" "" return a + b
Stel dat u een klasse hebt waarvoor autorisatie via een geheim nodig is voor al zijn vele methoden. Als de doorgewinterde Python-ontwikkelaar zou je waarschijnlijk kiezen voor een @ geautoriseerde methode-decorateur zoals in:
class SuperSecret (object): @authorized def f_1 (* args, secret): "" "" "" @autorized def f_2 (* args, secret): "" "" "" ... @authorized def f_100 (* args, secret ): "" "" ""
Dat is beslist een goede aanpak, maar het is een beetje vervelend om het herhaaldelijk te doen, vooral als je veel van zulke klassen hebt.
Meer kritisch: als iemand een nieuwe methode toevoegt en vergeet om de @ gemachtigde onderscheiding toe te voegen, hebt u een beveiligingsprobleem in uw handen. Geen schrik hebben. Python 3-klasse decorateurs hebben je terug. Met de volgende syntaxis kunt u (met de juiste definitie van de klassendecoratie) automatisch elke methode van de doelklassen autoriseren:
@ geautoriseerde klasse SuperSecret (object): def f_1 (* args, secret): "" "" "" def f_2 (* args, secret): "" "" "" ... def f_100 (* args, secret): "" "" ""
Het enige wat je hoeft te doen is de klas zelf te versieren. Merk op dat de binnenhuisarchitect slim kan zijn en een speciale methode zoals negeren __in het__() of kan worden geconfigureerd om indien nodig op een bepaalde subset van toepassing te zijn. De lucht (of je verbeeldingskracht) is de limiet.
Als je verdere voorbeelden wilt nastreven, bekijk dan de PythonDecoratorLibrary.
Nu je enkele voorbeelden in actie hebt gezien, is het tijd om de magie te onthullen. De formele definitie is dat een binnenhuisarchitect een opvraagbare persoon is die een opvraagbare (het doelwit) accepteert en een opvraagbare (de ingerichte) retourneert die dezelfde argumenten accepteert als het oorspronkelijke doelwit..
Woah! dat is een hoop woorden die onbegrijpelijk op elkaar liggen. Ten eerste, wat is een opvraagbaar? Een opvraagbare is slechts een Python-object met een __call __ () methode. Dat zijn meestal functies, methoden en klassen, maar u kunt een __call __ () methode op een van uw klassen en vervolgens zullen uw klasinstanties ook callables worden. Om te controleren of een Python-object opvraagbaar is, kunt u de ingebouwde functie callable () gebruiken:
callable (len) True callable ('123') False
Merk op dat de opvraagbare () functie is verwijderd uit Python 3.0 en teruggebracht in Python 3.2, dus als je om wat voor reden dan ook Python 3.0 of 3.1 gebruikt, moet je controleren of er een __call__ attribuut als in hasattr (len, '__call__')
.
Wanneer u een dergelijke decorateur gebruikt en deze met de @ -syntaxis toepast op een bepaalde opvraagbare waarde, wordt de oorspronkelijke opvraagbare waarde vervangen door de opvraagbare methode die door de decorateur is geretourneerd. Dit is misschien een beetje moeilijk te begrijpen, dus laten we dit illustreren door in de ingewanden van een paar eenvoudige decorateurs te kijken.
Een functie-decorateur is een decorateur die wordt gebruikt om een functie of een methode te versieren. Stel dat we de string willen afdrukken "Ja, het werkt!" elke keer dat een gedecoreerde functie of methode wordt aangeroepen voordat de oorspronkelijke functie daadwerkelijk wordt aangeroepen. Hier is een niet-decorateur manier om het te bereiken. Hier is de functie foo () die "foo () hier afdrukt":
def foo (): print 'foo () hier' foo () Uitvoer: foo () hier
Hier is de lelijke manier om het gewenste resultaat te bereiken:
original_foo = foo def decorated_foo (): print 'Ja, het werkt!' original_foo () foo = decorated_foo foo () Uitgang: Ja, het werkt! foo () hier
Er zijn verschillende problemen met deze aanpak:
Een binnenhuisarchitect die hetzelfde resultaat bereikt en ook herbruikbaar en configureerbaar is ziet er als volgt uit:
def yeah_it_works (f): def decorated (* args, ** kwargs): print 'Ja, het werkt' return f (* args, ** kwargs) retour gedecoreerd
Merk op dat yeah_it_works () een functie is (dus callable) die een opvraagbare ** f ** als een argument accepteert, en het geeft een opvraagbare waarde (de geneste functie ** gedecoreerd **) die elk aantal en soorten argumenten accepteert.
Nu kunnen we het op elke functie toepassen:
@yeah_it_works def f1 () print 'f1 () hier' @yeah_it_works def f2 () print 'f3 () hier' @yeah_it_works def f3 () print 'f3 () hier' f1 () f2 () f3 () Uitvoer: Ja, het werkt f1 () hier Ja, het werkt f2 () hier Ja, het werkt f3 () hier
Hoe werkt het? Het origineel f1, f2 en f3 functies zijn vervangen door de geneste functie die is geretourneerd door yeah_it_works. Voor elke afzonderlijke functie wordt het vastgelegd f opvraagbaar is de originele functie ( f1, f2 of f3), dus de gedecoreerde functie is anders en doet het goede ding, dat is afgedrukt "Ja, het werkt!" en dan de oorspronkelijke functie aanroepen f.
Klasse decorateurs werken op een hoger niveau en versieren een hele klas. Hun effect vindt plaats in de tijd van de klassedefinitie. Je kunt ze gebruiken om methoden van elke versierde klasse toe te voegen of te verwijderen, of zelfs om decorateurs toe te passen op een hele reeks methoden.
Stel dat we alle uitzonderingen van een bepaalde klasse in een klassenattribuut willen bijhouden. Laten we aannemen dat we al een functie-decorator hebben genoemd track_exceptions_decorator die deze functionaliteit uitvoert. Zonder een klassendecorateur kunt u deze handmatig op elke methode of toevlucht toepassen op metaclasses. Bijvoorbeeld:
klasse A (object): @track_exceptions_decorator def f1 (): ... @track_exceptions_decorator def f2 (): ... @track_exceptions_decorator def f100 (): ...
Een klassendecorateur die hetzelfde resultaat bereikt, is:
def track_exception (cls): # Verkrijg alle opvraagbare attributen van de klasse callable_attributes = k: v voor k, v in cls .__ dict __. items () indien opvraagbaar (v) # Versier elk opvraagkenmerk van naar de invoerklasse voor naam , func in callable_attributes.items (): decorated = track_exceptions_decorator (func) setattr (cls, name, decorated) return cls @track_exceptions class A: def f1 (self): print ('1') def f2 (self): print ( '2')
Python staat bekend om zijn flexibiliteit. Decorateurs brengen het naar het volgende niveau. U kunt horizontale problemen in herbruikbare decorateurs verpakken en toepassen op functies, methoden en hele klassen. Ik raad ten zeerste aan dat elke serieuze ontwikkelaar van Python bekend raakt met decorateurs en ten volle gebruik maakt van hun voordelen.