DROOG je Python-code met decorateurs

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:

  • wat zijn decorateurs van Python en waar ze goed voor zijn
  • hoe onze eigen decorateurs te definiëren
  • voorbeelden van echte decorateurs en hoe ze werken
  • hoe je betere code kunt schrijven met decorateurs

Invoering

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:

  1. Accepteer een functie als een argument
  2. Stuur een andere functie terug

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_2x_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.

Bouw je eigen decorateur

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 praktisch voorbeeld

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

Decorateurs in het wild

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: 

  1. pagina's die u kunt bekijken zonder te worden geverifieerd (voorpagina, landingspagina, blogpost, login, register)
  2. pagina's die moeten worden geverifieerd om te bekijken (profielinstellingen, inbox, dashboard)

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.

conclusies

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:

  • Correct gebruik en ontwerp van decorateurs kan uw code verbeteren, schoner en mooier maken.
  • Als u decorateurs gebruikt, kunt u uw code DROOGZAKEN - verplaats dezelfde code van interne functies naar decorateurs.
  • Naarmate u decorateurs meer gebruikt, zult u betere, complexere manieren vinden om ze te gebruiken.

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.

!