Los decoradores son una de las características más agradables de Python, pero para el programador principiante de Python, pueden parecer magia. El propósito de este artículo es comprender, en profundidad, el mecanismo detrás de los decoradores de Python..
Esto es lo que aprenderás:
En caso de que no hayas visto uno todavía (o quizás no sabías que estabas tratando con uno), los decoradores se ven así:
@decorator def function_to_decorate (): pass
Por lo general, los encuentras por encima de la definición de una función, y su prefijo es @
. Los decoradores son especialmente buenos para mantener su código. SECO (No se repita), y lo hacen al mismo tiempo que mejoran la legibilidad de su código.
¿Todavía borroso? No lo seas, ya que los decoradores son solo funciones de Python. ¡Está bien! Ya sabes como crear uno. De hecho, el principio fundamental detrás de los decoradores es la composición de la función. Tomemos un ejemplo:
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 # Let's componer las dos funciones para x = 2 imprimir (x_squared (x_plus_2 (2))) # (2 + 2) ^ 2 == 16 imprimir (x_squared (x_plus_2 (3))) # (3 + 2) ^ 2 == 25 imprimir (x_squared (x_plus_2 (4))) # (4 + 2) ^ 2 == 36
¿Y si quisiéramos crear otra función?, x_plus_2_squared
? Tratar de componer las funciones sería inútil:
x_squared (x_plus_2) # TypeError: tipo (s) de operando no compatibles para *: 'function' y 'function'
No puede componer funciones de esta manera porque ambas funciones toman números como argumentos. Sin embargo, esto funcionará:
# Ahora creamos una composición de función adecuada sin aplicar realmente la función 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 impresión (x_plus_2_squared (4)) # (4 + 2) ^ 2 == 36
Vamos a redefinir cómo x_squared
trabajos. Si queremos x_squared
para ser compostable por defecto, debe:
Nombraremos la versión componible de x_squared
simplemente al cuadrado
.
def al cuadrado (func): devolver lambda x: func (x) * func (x) imprimir (al cuadrado (x_plus_2) (2)) # (2 + 2) ^ 2 == 16 imprimir (al cuadrado (x_plus_2) (3)) # (3 + 2) ^ 2 == 25 impresión (al cuadrado (x_plus_2) (4)) # (4 + 2) ^ 2 == 36
Ahora que hemos definido el al cuadrado
funciona de una manera que lo hace composable, podemos usarlo con cualquier otra función. Aquí hay unos ejemplos:
def x_plus_3 (x): devolver x + 3 def x_times_2 (x): devolver x * 2 imprimir (cuadrado (x_plus_3) (2)) # (2 + 3) ^ 2 == 25 imprimir (cuadrado (x_times_2) (2) ) # (2 * 2) ^ 2 == 16
Podemos decir eso al cuadrado
decora las funciones x_plus_2
, x_plus_3
, y x_times_2
. Estamos muy cerca de lograr la notación de decorador estándar. Mira esto:
x_plus_2 = cuadrado (x_plus_2) # Decoramos x_plus_2 con impresión al cuadrado (x_plus_2 (2)) # x_plus_2 ahora devuelve el resultado cuadrado decorado: (2 + 2) ^ 2
Eso es! x_plus_2
Es una función decorada propia de Python. Aquí es donde el @
la notación entra en su lugar
def x_plus_2 (x): return x + 2 x_plus_2 = squared (x_plus_2) # ^ Esto es completamente equivalente a: @squared def x_plus_2 (x): return x + 2
De hecho, la @
La notación es una forma de azúcar sintáctica. Vamos a probar eso:
@squared def x_times_3 (x): return 3 * x print (x_times_3 (2)) # (3 * 2) ^ 2 = 36. # Puede que sea un poco confuso, pero al decorarlo con el cuadrado, x_times_3 se convirtió en un hecho ( 3 * x) * (3 * x) @squared def x_minus_1 (x): devolver x - 1 impresión (x_minus_1 (3)) # (3 - 1) ^ 2 = 4
Si al cuadrado
es el primer decorador que has escrito, date una gran palmadita en la espalda. Has captado uno de los conceptos más complejos de Python. En el camino, aprendiste otra característica fundamental de los lenguajes de programación funcionales: composición de la función.
Un decorador es una función que toma una función como argumento y devuelve otra función. Dicho esto, la plantilla genérica para definir un decorador es:
def decorator (function_to_decorate): #… return decorated_function
En caso de que no lo sepas, puedes definir funciones dentro de las funciones. En la mayoría de los casos, función decorada
será definido dentro decorador
.
def decorator (function_to_decorate): def decorated_function (* args, ** kwargs): #… Ya que decoramos 'function_to_decorate', deberíamos usarlo en algún lugar dentro de aquí. volver decoration_function
Veamos un ejemplo más práctico:
importar pytz desde datetime importar datetime def to_utc (function_to_decorate): def decorated_function (): # Obtener el resultado de function_to_decorate y transformar el resultado en UTC return function_to_decorate (). astimezone (pytz.utc) return decorated_function @to_utc def package_pick "" Esto puede provenir de una base de datos o de una API "" "tz = pytz.timezone ('US / Pacific') devolver tz.localize (datetime (2017, 8, 2, 12, 30, 0, 0)) @ to_utc def package_delivery_time (): "" "Esto puede venir de una base de datos o de una API" "" tz = pytz.timezone ('US / Eastern') return tz.localize (datetime (2017, 8, 2, 12, 30 , 0, 0)) # ¡Qué coincidencia, en la misma zona horaria de tiempo diferente! print ("PICKUP:", package_pickup_time ()) # '2017-08-02 19: 30: 00 + 00: 00' print ("ENTREGA:", package_delivery_time ()) # '2017-08-02 16:30: 00 + 00: 00 '
¡Dulce! Ahora puede estar seguro de que todo dentro de su aplicación está estandarizado para la zona horaria UTC.
Otro caso de uso realmente popular y clásico para decoradores es el almacenamiento en caché del resultado de una función:
tiempo de importación def cached (function_to_decorate): _cache = # Donde guardamos los resultados def decorated_function (* args): start_time = time.time () print ('_ cache:', _cache) si args no está en _cache: _cache [args ] = function_to_decorate (* args) # Realice el cálculo y guárdelo en la impresión de caché ('Compute time:% ss'% round (time.time () - start_time, 2)) return _cache [args] return decorated_function @cached def complex_computation (x, y): imprimir ('Procesando ...') time.sleep (2) devolver x + y imprimir (complex_computation (1, 2)) # 3, Realizar la operación de impresión costosa (complex_computation (1, 2)) # 3 , SKIP realiza la impresión de operación costosa (complex_computation (4, 5)) # 9, Realiza la impresión de operación costosa (complex_computation (4, 5)) # 9, SKIP realiza la impresión de operación costosa (complex_computation (1, 2)) # 3 , SKIP realizando la operación costosa
Si miras el código de manera superficial, puedes objetar. El decorador no es reutilizable! Si decoramos otra función (digamos another_complex_computation
) y llámelo con los mismos parámetros, luego obtendremos los resultados en caché de la función complex_computation
. Esto no sucederá. El decorador es reutilizable, y he aquí por qué:
@cached def another_complex_computation (x, y): print ('Processing ...') time.sleep (2) return x * y print (another_complex_computation (1, 2)) # 2, Realizando la impresión de operación costosa (another_complex_computation (1, 2 )) # 2, SKIP realizando la impresión de la operación costosa (another_complex_computation (1, 2)) # 2, SKIP realizando la operación costosa
los en caché
La función se llama una vez para cada función que decora, por lo que una diferente _cache
La variable se instancia cada vez y vive en ese contexto. Vamos a probar esto:
print (complex_computation (10, 20)) # -> 30 print (another_complex_computation (10, 20)) # -> 200
El decorador que acabamos de codificar, como habrás notado, es muy útil. Es tan útil que ya existe una versión más compleja y robusta en el estándar. functools
módulo. Se llama lru_cache
. LRU es la abreviatura de Menos usado recientemente, una estrategia de almacenamiento en caché.
desde functools import lru_cache @lru_cache () def complex_computation (x, y): print ('Processing ...') time.sleep (2) return x + y print (complex_computation (1, 2)) # Processing ... 3 print (complex_computation ( 1, 2)) # 3 imprimir (complex_computation (2, 3)) # Procesando ... 5 imprimir (complex_computation (1, 2)) # 3 imprimir (complex_computation (2, 3)) # 5
Uno de mis usos favoritos de los decoradores es el marco web de Flask. Es tan claro que este fragmento de código es lo primero que ve en el sitio web de Flask. Aquí está el fragmento:
desde el matraz importar Flask app = Flask (__ name__) @ app.route ("/") def hello (): return "Hello World!" si __name__ == "__main__": app.run ()
los app.ruta
El decorador asigna la función. Hola
como el manejador de solicitudes para la ruta "/"
. La simplicidad es asombrosa..
Otro buen uso de decoradores es dentro de Django. Normalmente, las aplicaciones web tienen dos tipos de páginas:
Si intenta ver una página de este último tipo, normalmente será redirigido a una página de inicio de sesión. Aquí es cómo implementar eso en Django:
desde django.http importar HttpResponse desde django.contrib.auth.decorators importar login_required # Páginas públicas def home (solicitud): devolver HttpResponse ("Casa") def landing (solicitud): devolver HttpResponse ("Aterrizaje") # Páginas autenticadas @login_required (login_url = '/ login') def dashboard (request): return HttpResponse ("Tablero") @login_required (login_url = '/ login') def profile_settings (solicitud): devolver HttpResponse ("Configuración de perfil")
Observe lo bien que están marcadas las vistas privadas Necesario iniciar sesión
. Al pasar por el código, es muy claro para el lector qué páginas requieren que el usuario inicie sesión y cuáles no..
Espero que te hayas divertido aprendiendo acerca de los decoradores porque representan una característica muy elegante de Python. Aquí hay algunas cosas para recordar:
Recuerde ver lo que tenemos disponible para la venta y para estudiar en Envato Market, y no dude en hacer cualquier pregunta y proporcionar sus valiosos comentarios utilizando el siguiente feed..
!