Decorateurs zijn een van de leukste functies van Python, maar voor de beginner Python-programmeur kunnen ze als magisch lijken. Het doel van dit artikel is om, in de diepte, het mechanisme achter de decorateurs van Python te begrijpen.
Dit is wat je leert:
Als je er nog geen hebt gezien (of misschien wist je niet dat je er een had), zien decorateurs er zo uit:
@decorator def function_to_decorate (): pass
Je komt ze meestal tegen boven de definitie van een functie, en ze worden voorafgegaan door @
. Decorateurs zijn vooral goed om je code te houden DRY (Do not Repeat Yourself), en dat doen ze terwijl ze ook de leesbaarheid van uw code verbeteren.
Nog steeds wazig? Doe dat niet, want decorateurs zijn gewoon Python-functies. Dat is juist! Je weet al hoe je er een kunt maken. Het fundamentele principe achter decorateurs is eigenlijk de functiesamenstelling. Laten we een voorbeeld nemen:
def x_plus_2 (x): return x + 2 print (x_plus_2 (2)) # 2 + 2 == 4 def x_squared (x): return x * x print (x_squared (3)) # 3 ^ 2 == 9 # Laten we stel de twee functies samen voor x = 2 afdrukken (x_squared (x_plus_2 (2))) # (2 + 2) ^ 2 == 16 afdrukken (x_squared (x_plus_2 (3))) # (3 + 2) ^ 2 == 25 print (x_squared (x_plus_2 (4))) # (4 + 2) ^ 2 == 36
Wat als we een andere functie wilden creëren?, x_plus_2_squared
? Proberen om de functies samen te stellen zou nutteloos zijn:
x_squared (x_plus_2) # TypeError: niet-ondersteund operandtype (n) voor *: 'functie' en 'functie'
U kunt op deze manier geen functies samenstellen, omdat beide functies getallen als argumenten gebruiken. Dit zal echter werken:
# Laten we nu een goede functiesamenstelling maken zonder de functie daadwerkelijk toe te passen x_plus_2_squared = lambda x: x_squared (x_plus_2 (x)) print (x_plus_2_squared (2)) # (2 + 2) ^ 2 == 16 print (x_plus_2_squared (3)) # (3 + 2) ^ 2 == 25 afdrukken (x_plus_2_squared (4)) # (4 + 2) ^ 2 == 36
Laten we opnieuw definiëren hoe x_squared
werken. Als we willen x_squared
om standaard configureerbaar te zijn, zou het:
We noemen de configureerbare versie van x_squared
eenvoudigweg kwadraat
.
def squared (func): return lambda x: func (x) * func (x) print (squared (x_plus_2) (2)) # (2 + 2) ^ 2 == 16 print (squared (x_plus_2) (3)) # (3 + 2) ^ 2 == 25 afdrukken (vierkant (x_plus_2) (4)) # (4 + 2) ^ 2 == 36
Nu dat we het hebben gedefinieerd kwadraat
functioneer op een manier die het componeerbaar maakt, we kunnen het gebruiken met elke andere functie. Hier zijn enkele voorbeelden:
def x_plus_3 (x): return x + 3 def x_times_2 (x): return x * 2 print (vierkant (x_plus_3) (2)) # (2 + 3) ^ 2 == 25 print (vierkant (x_times_2) (2) ) # (2 * 2) ^ 2 == 16
We kunnen stellen dat kwadraat
siert de functies x_plus_2
, x_plus_3
, en x_times_2
. We zijn heel dicht bij het bereiken van de standaardnotatie voor decorateurs. Bekijk dit:
x_plus_2 = squared (x_plus_2) # We hebben x_plus_2 gedecoreerd met vierkante afdruk (x_plus_2 (2)) # x_plus_2 retourneert nu het gedecoreerde gekwadrateerde resultaat: (2 + 2) ^ 2
Dat is het! x_plus_2
is een goede Python-versierde functie. Hier is waar de @
notatie komt op zijn plaats:
def x_plus_2 (x): return x + 2 x_plus_2 = squared (x_plus_2) # ^ Dit is volledig gelijk aan: @squared def x_plus_2 (x): return x + 2
In feite is de @
notatie is een vorm van syntactische suiker. Laten we dat eens proberen:
@squared def x_times_3 (x): return 3 * x print (x_times_3 (2)) # (3 * 2) ^ 2 = 36. # Het is misschien een beetje verwarrend, maar door het te versieren met vierkant, werd x_times_3 in feite ( 3 * x) * (3 * x) @squared def x_minus_1 (x): return x - 1 print (x_minus_1 (3)) # (3 - 1) ^ 2 = 4
Als kwadraat
is de eerste decorateur die je ooit hebt geschreven, geef jezelf een schouderklopje. Je hebt een van de meest complexe concepten in Python begrepen. Onderweg leerde je nog een fundamenteel kenmerk van functionele programmeertalen: functiesamenstelling.
Een decorateur is een functie die een functie als argument op zich neemt en een andere functie retourneert. Dat gezegd hebbende, is de generieke template voor het definiëren van een decorateur:
def decorator (function_to_decorate): # ... return decorated_function
Als u het niet wist, kunt u functies binnen functies definiëren. In de meeste gevallen is de decorated_function
zal binnen worden gedefinieerd decorateur
.
def decorator (function_to_decorate): def decorated_function (* args, ** kwargs): # ... Aangezien we 'function_to_decorate' versieren, zouden we het ergens hierbinnen moeten gebruiken. return decorated_function
Laten we een meer praktisch voorbeeld bekijken:
import pytz uit datetime import datetime def to_utc (function_to_decorate): def decorated_function (): # Haal het resultaat van function_to_decorate en transformeer het resultaat naar UTC return function_to_decorate (). astimezone (pytz.utc) return decorated_function @to_utc def package_pickup_time (): " "" Dit kan afkomstig zijn van een database of van een API "" "tz = pytz.timezone ('US / Pacific') return tz.localize (datetime (2017, 8, 2, 12, 30, 0, 0)) @ to_utc def package_delivery_time (): "" "Dit kan afkomstig zijn van een database of van een API" "" tz = pytz.timezone ('US / Eastern') return tz.localize (datetime (2017, 8, 2, 12, 30) , 0, 0)) # Wat een toeval, dezelfde tijd verschillende tijdzone! print ("PICKUP:", package_pickup_time ()) # '2017-08-02 19: 30: 00 + 00: 00' print ("DELIVERY:", package_delivery_time ()) # '2017-08-02 16:30: 00 + 00: 00'
Zoet! Nu kunt u er zeker van zijn dat alles in uw app is gestandaardiseerd voor de UTC-tijdzone.
Een ander echt populair en klassiek gebruik-geval voor decorateurs caching het resultaat van een functie:
import time def cached (function_to_decorate): _cache = # Waarbij we de resultaten behouden def decorated_function (* args): start_time = time.time () print ('_ cache:', _cache) als args niet in _cache: _cache [args ] = function_to_decorate (* args) # Voer de berekening uit en sla deze op in de cachedruk ('Bereken tijd:% ss'% ronde (time.time () - start_tijd, 2)) return _cache [args] return decorated_function @cached def complex_computation (x, y): print ('Verwerken ...') time.sleep (2) return x + y print (complex_computation (1, 2)) # 3, De dure bewerking afdrukken (complex_computation (1, 2)) # 3 , SKIP voert de dure bewerkingsafdruk uit (complex_computation (4, 5)) # 9, voert de dure bewerkingsafdruk uit (complex_computation (4, 5)) # 9, SKIP voert de dure bewerkingsafdruk uit (complex_computation (1, 2)) # 3 , SKIP voert de dure bewerking uit
Als u de code onduidelijk bekijkt, kunt u bezwaar maken. De binnenhuisarchitect is niet herbruikbaar! Als we een andere functie versieren (laten we zeggen another_complex_computation
) en noem het met dezelfde parameters dan krijgen we de resultaten in de cache van de complex_computation-functie
. Dit zal niet gebeuren. De binnenhuisarchitect is herbruikbaar en dit is waarom:
@cached def another_complex_computation (x, y): print ('Processing ...') time.sleep (2) return x * y print (another_complex_computation (1, 2)) # 2, Het uitvoeren van de dure bewerking print (another_complex_computation (1, 2 )) # 2, SKIP voert de dure bewerking uit (another_complex_computation (1, 2)) # 2, SKIP voert de dure bewerking uit
De gecached
functie wordt eenmaal aangeroepen voor elke functie die het decoreert, dus een andere _cache
variabele wordt elke keer geïnstantieerd en leeft in die context. Laten we dit uittesten:
print (complex_computation (10, 20)) # -> 30 print (another_complex_computation (10, 20)) # -> 200
De decorateur die we net hebben gecodeerd, zoals je misschien hebt gemerkt, is erg handig. Het is zo handig dat er al een meer complexe en robuuste versie in de standaard bestaat functools
module. Het heet lru_cache
. LRU is de afkorting van Minst recentelijk gebruikt, een caching-strategie.
van functools import lru_cache @lru_cache () def complex_computation (x, y): print ('Verwerken ...') time.sleep (2) return x + y print (complex_computation (1, 2)) # Verwerking ... 3 print (complex_computation ( 1, 2)) # 3 afdrukken (complex_computation (2, 3)) # Verwerken ... 5 afdrukken (complex_computation (1, 2)) # 3 afdrukken (complex_computation (2, 3)) # 5
Een van mijn favoriete toepassingen van decorateurs is in het Flask-webraamwerk. Het is zo netjes dat dit codefragment het eerste is dat u op de Flask-website ziet. Hier is het fragment:
uit de flacon importeren Flask app = Flask (__ name__) @ app.route ("/") def hello (): retourneer "Hello World!" if __name__ == "__main__": app.run ()
De app.route
decorateur wijst de functie toe Hallo
als de verzoekbehandelaar voor de route "/"
. De eenvoud is geweldig.
Een ander net gebruik van decorateurs is binnen Django. Gewoonlijk hebben webtoepassingen twee soorten pagina's:
Als u een pagina van dit laatste type probeert te bekijken, wordt u meestal doorgestuurd naar een inlogpagina. Hier is de manier om dat in Django te implementeren:
van django.http import HttpResponse van django.contrib.auth.decorators import login_required # Public Pages def home (verzoek): return HttpResponse ("Huis") def landing (request): return HttpResponse ("Landen") # Geverifieerde pagina's @login_required (login_url = '/ login') def dashboard (request): return HttpResponse ("Dashboard") @login_required (login_url = '/ login') def profile_settings (request): return HttpResponse ("Profielinstellingen")
Observeer hoe netjes de privé-weergaven zijn gemarkeerd Aanmelden vereist
. Tijdens het doorlopen van de code is het voor de lezer heel duidelijk op welke pagina's de gebruiker moet inloggen en welke pagina's dat niet doen.
Ik hoop dat je het leuk vond om over decorateurs te leren, omdat ze een heel nette Python-functie vertegenwoordigen. Hier zijn enkele dingen om te onthouden:
Vergeet niet om te bekijken wat we beschikbaar hebben voor de verkoop en om te studeren op Envato Market, en aarzel niet om vragen te stellen en uw waardevolle feedback te geven met behulp van de onderstaande feed.
!