Seca tu código de Python con decoradores

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:

  • ¿Qué son los decoradores de Python y para qué sirven?
  • Cómo definir nuestros propios decoradores.
  • Ejemplos de decoradores del mundo real y cómo funcionan.
  • Cómo escribir mejor código usando decoradores.

Introducción

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:

  1. Aceptar una función como argumento.
  2. Devolver otra función

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

Construye tu propio decorador

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.

Un ejemplo práctico

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

Decoradores en la naturaleza

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: 

  1. páginas que puede ver sin estar autenticado (portada, página de destino, publicación de blog, inicio de sesión, registro)
  2. páginas para las que debe estar autenticado (configuración de perfil, bandeja de entrada, panel de control)

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

Conclusiones

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:

  • El uso y diseño correctos de los decoradores pueden hacer que su código sea mejor, más limpio y más hermoso.
  • El uso de decoradores puede ayudarlo a SECAR su código: mueva el código idéntico de las funciones internas a los decoradores.
  • A medida que usa más los decoradores, encontrará formas mejores y más complejas de usarlos..

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

!