ASCIUGA il tuo codice Python con decoratori

I decoratori sono una delle più belle funzionalità di Python, ma per il principiante programmatore Python, possono sembrare magici. Lo scopo di questo articolo è quello di comprendere, in modo approfondito, il meccanismo dietro i decoratori Python.

Ecco cosa imparerai:

  • cosa sono i decoratori Python e per cosa sono adatti
  • come definire i nostri decoratori
  • esempi di decoratori del mondo reale e come funzionano
  • come scrivere un codice migliore usando decoratori

introduzione

Nel caso in cui non ne hai ancora visto uno (o forse non sapevi di averne a che fare con uno), i decoratori assomigliano a questo:

@decorator def function_to_decorate (): pass

Di solito li incontri sopra la definizione di una funzione, e sono preceduti da @. I decoratori sono particolarmente buoni per mantenere il tuo codice ASCIUTTO (non ripeti te stesso), e lo fanno migliorando allo stesso tempo la leggibilità del tuo codice.

Ancora sfocato? Non essere, dal momento che i decoratori sono solo funzioni di Python. Giusto! Sai già come crearne uno. In effetti, il principio fondamentale dietro i decoratori è la composizione delle funzioni. Facciamo un esempio:

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 # Passiamo componi le due funzioni per x = 2 stampa (x_squared (x_plus_2 (2))) # (2 + 2) ^ 2 == 16 stampa (x_squared (x_plus_2 (3))) # (3 + 2) ^ 2 == 25 print (x_squared (x_plus_2 (4))) # (4 + 2) ^ 2 == 36

Cosa succede se volessimo creare un'altra funzione, x_plus_2_squared? Cercare di comporre le funzioni sarebbe inutile:

x_squared (x_plus_2) # TypeError: tipo / i di operando non supportati per *: 'function' e 'function'

Non è possibile comporre le funzioni in questo modo perché entrambe le funzioni accettano numeri come argomenti. Tuttavia, questo funzionerà:

# Ora creiamo una composizione di funzione corretta senza applicare effettivamente la funzione 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 stampa (x_plus_2_squared (4)) # (4 + 2) ^ 2 == 36

Ridefiniamo come x_squared lavori. Se vogliamo x_squared per essere componibili per impostazione predefinita, dovrebbe:

  1. Accetta una funzione come argomento
  2. Restituire un'altra funzione

Chiameremo la versione componibile di x_squared semplicemente quadrato.

def squared (func): return lambda x: func (x) * func (x) stampa (quadrato (x_plus_2) (2)) # (2 + 2) ^ 2 == 16 stampa (quadrato (x_plus_2) (3)) # (3 + 2) ^ 2 == 25 stampa (al quadrato (x_plus_2) (4)) # (4 + 2) ^ 2 == 36

Ora che abbiamo definito il quadrato funziona in un modo che lo rende componibile, possiamo usarlo con qualsiasi altra funzione. Ecco alcuni esempi:

def x_plus_3 (x): return x + 3 def x_times_2 (x): return x * 2 print (quadrato (x_plus_3) (2)) # (2 + 3) ^ 2 == 25 print (quadrato (x_times_2) (2) ) # (2 * 2) ^ 2 == 16

Possiamo dirlo quadrato decora le funzioni x_plus_2x_plus_3, e x_times_2. Siamo molto vicini al raggiungimento della notazione standard del decoratore. Controllalo:

x_plus_2 = quadrato (x_plus_2) # Abbiamo decorato x_plus_2 con stampa al quadrato (x_plus_2 (2)) # x_plus_2 ora restituisce il risultato quadrato decorato: (2 + 2) ^ 2 

Questo è tutto! x_plus_2 è una funzione decorata in Python. Ecco dove il @ la notazione entra in vigore:

def x_plus_2 (x): return x + 2 x_plus_2 = quadrato (x_plus_2) # ^ Questo è completamente equivalente con: @squared def x_plus_2 (x): return x + 2

In effetti, il @ la notazione è una forma di zucchero sintattico. Proviamoci:

@squared def x_times_3 (x): restituisce 3 * x print (x_times_3 (2)) # (3 * 2) ^ 2 = 36. # Potrebbe essere un po 'di confusione, ma decorandolo con il quadrato, x_times_3 è diventato di fatto ( 3 * x) * (3 * x) @squared def x_minus_1 (x): restituisce x - 1 stampa (x_minus_1 (3)) # (3 - 1) ^ 2 = 4

Se quadrato è il primo decoratore che tu abbia mai scritto, datti una grossa pacca sulla spalla. Hai colto uno dei concetti più complessi in Python. Lungo la strada, hai imparato un'altra caratteristica fondamentale dei linguaggi di programmazione funzionale: composizione della funzione.

Costruisci il tuo decoratore

Un decoratore è una funzione che accetta una funzione come argomento e restituisce un'altra funzione. Detto questo, il modello generico per la definizione di un decoratore è:

def decorator (function_to_decorate): # ... return decorate_function

Nel caso in cui non lo sapessi, puoi definire le funzioni all'interno delle funzioni. Nella maggior parte dei casi, il decorated_function sarà definito all'interno decoratore.

def decorator (function_to_decorate): def decorate_function (* args, ** kwargs): # ... Dato che decoriamo 'function_to_decorate', dovremmo usarlo da qualche parte qui dentro return decorated_function

Diamo un'occhiata ad un esempio più pratico:

import pytz da datetime import datetime def to_utc (function_to_decorate): def decorated_function (): # Ottieni il risultato di function_to_decorate e trasforma il risultato in UTC return function_to_decorate (). astimezone (pytz.utc) return decorate_function @to_utc def package_pickup_time (): " "" Può provenire da un database o da un'API "" "tz = pytz.timezone ('US / Pacific') return tz.localize (datetime (2017, 8, 2, 12, 30, 0, 0)) @ to_utc def package_delivery_time (): "" "Può provenire da un database o da un'API" "" tz = pytz.timezone ('US / Eastern') return tz.localize (datetime (2017, 8, 2, 12, 30 , 0, 0)) # Che coincidenza, fuso orario diverso nello stesso tempo! 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'

Dolce! Ora puoi essere sicuro che tutto all'interno della tua app è standardizzato per il fuso orario UTC.

Un esempio pratico

Un altro caso d'uso molto popolare e classico per i decoratori è il caching del risultato di una funzione:

import time def cache (function_to_decorate): _cache =  # Dove manteniamo i risultati def decorated_function (* args): start_time = time.time () print ('_ cache:', _cache) se args non in _cache: _cache [args ] = function_to_decorate (* args) # Esegue il calcolo e lo memorizza nella cache print ('Tempo di calcolo:% ss'% round (time.time () - start_time, 2)) return _cache [args] return decorate_function @cached def complex_computation (x, y): print ('Elaborazione ...') time.sleep (2) restituisce x + y print (complex_computation (1, 2)) # 3, Esecuzione della costosa operazione di stampa (complex_computation (1, 2)) # 3 , SKIP esegue la costosa operazione di stampa (complex_computation (4, 5)) # 9, Eseguendo la costosa operazione di stampa (complex_computation (4, 5)) # 9, SKIP esegue la costosa operazione di stampa (complex_computation (1, 2)) # 3 , SKIP esegue l'operazione costosa

Se osservi superficialmente il codice, potresti obiettare. Il decoratore non è riutilizzabile! Se decoriamo un'altra funzione (diciamo another_complex_computation) e chiamiamolo con gli stessi parametri, quindi otterremo i risultati memorizzati nella cache funzione complex_computation. Questo non succederà. Il decoratore è riutilizzabile, ed ecco perché:

@cached def another_complex_computation (x, y): print ('Processing ...') time.sleep (2) restituisce x * y print (another_complex_computation (1, 2)) # 2, Esecuzione della costosa operazione di stampa (another_complex_computation (1, 2 )) # 2, SKIP esegue la costosa operazione di stampa (another_complex_computation (1, 2)) # 2, SKIP esegue l'operazione costosa

Il cache la funzione è chiamata una volta per ogni funzione che decora, quindi una diversa _cache variabile è istanziata ogni volta e vive in quel contesto. Proviamo questo:

print (complex_computation (10, 20)) # -> 30 print (another_complex_computation (10, 20)) # -> 200

Decoratori in natura

Il decoratore che abbiamo appena codificato, come avrete notato, è molto utile. È così utile che una versione più complessa e robusta esiste già nello standard functools modulo. È chiamato lru_cache. LRU è l'abbreviazione di Utilizzato ancora di recente, una strategia di caching. 

da functools import lru_cache @lru_cache () def complex_computation (x, y): print ('Processing ...') time.sleep (2) restituisce x + y print (complex_computation (1, 2)) # Processing ... 3 print (complex_computation ( 1, 2)) # 3 print (complex_computation (2, 3)) # Processing ... 5 print (complex_computation (1, 2)) # 3 print (complex_computation (2, 3)) # 5

Uno dei miei usi preferiti dei decoratori è nel framework web di Flask. È così chiaro che questo frammento di codice è la prima cosa che vedi sul sito web di Flask. Ecco il frammento:

da flask import Flask app = Flask (__ name__) @ app.route ("/") def ciao (): ritorna "Hello World!" se __name__ == "__main__": app.run ()

Il app.route decoratore assegna la funzione Ciao come gestore della richiesta per il percorso "/". La semplicità è incredibile. 

Un altro uso pulito dei decoratori è all'interno di Django. Di solito, le applicazioni web hanno due tipi di pagine: 

  1. pagine che è possibile visualizzare senza essere autenticati (prima pagina, pagina di destinazione, post di blog, accesso, registro)
  2. pagine che devi autenticare per visualizzare (impostazioni profilo, casella di posta, dashboard)

Se si tenta di visualizzare una pagina di quest'ultimo tipo, di solito viene reindirizzato a una pagina di accesso. Ecco come implementarlo in Django:

da django.http import HttpResponse da django.contrib.auth.decorators import login_quisito # Pagine pubbliche def home (richiesta): return HttpResponse ("Casa") def landing (request): restituisci HttpResponse ("atterraggio") # Authenticated Pages @login_required (login_url = '/ login') dashboard def (richiesta): restituisce HttpResponse ("Cruscotto") @login_required (login_url = '/ login') def profile_settings (richiesta): restituisce HttpResponse ("Impostazioni del profilo")

Osserva in che modo le viste private sono contrassegnate con Accesso richiesto. Mentre si passa attraverso il codice, è molto chiaro per il lettore quali pagine richiedono all'utente di accedere e quali no.

conclusioni

Spero ti sia divertito a conoscere i decoratori perché rappresentano una funzionalità Python molto accurata. Ecco alcune cose da ricordare:

  • Utilizzare e progettare correttamente i decoratori può rendere il tuo codice migliore, più pulito e più bello.
  • L'utilizzo dei decoratori può aiutarti a ASCIUGARE il codice, spostare codice identico dalle funzioni interne ai decoratori.
  • Mentre usi di più i decoratori, troverai modi migliori e più complessi per utilizzarli.

Ricordati di controllare ciò che abbiamo a disposizione per la vendita e per studiare su Envato Market, e non esitare a fare domande e fornire il tuo prezioso feedback usando il feed qui sotto.

!