Course
Tutorial de Decoradores Python
Un decorador es un patrón de diseño en Python que permite al usuario añadir nuevas funciones a un objeto existente sin modificar su estructura. Los decoradores suelen aplicarse a las funciones, y desempeñan un papel crucial a la hora de mejorar o modificar el comportamiento de las funciones. Tradicionalmente, los decoradores se colocan antes de la definición de una función que quieras decorar. En este tutorial, demostraremos cómo utilizar eficazmente los decoradores en las funciones de Python.
Las funciones en Python son ciudadanos de primera clase. Esto significa que admiten operaciones como ser pasados como argumento, devueltos por una función, modificados y asignados a una variable. Esta propiedad es crucial, ya que permite que las funciones se traten como cualquier otro objeto en Python, lo que permite una mayor flexibilidad en la programación.
Para ejecutar fácilmente tú mismo todo el código de ejemplo de este tutorial, puedes crear gratuitamente un libro de trabajo DataLab que tenga Python preinstalado y contenga todos los ejemplos de código. Para practicar más con los decoradores, consulta este ejercicio práctico de DataCamp.
Aprende Python desde cero
Asignar funciones a variables
Para empezar, creamos una función que sumará uno a un número cada vez que sea llamada. A continuación, asignaremos la función a una variable y utilizaremos esta variable para llamar a la función.
def plus_one(number):
return number + 1
add_one = plus_one
add_one(5)
6
Definir funciones dentro de otras funciones
A continuación, ilustraremos cómo puedes definir una función dentro de otra función en Python. Quédate conmigo, pronto descubriremos cómo todo esto es relevante para crear y comprender los decoradores en Python.
def plus_one(number):
def add_one(number):
return number + 1
result = add_one(number)
return result
plus_one(4)
5
Pasar funciones como argumentos a otras funciones
Las funciones también pueden pasarse como parámetros a otras funciones. Ilustrémoslo a continuación.
def plus_one(number):
return number + 1
def function_call(function):
number_to_add = 5
return function(number_to_add)
function_call(plus_one)
6
Funciones que devuelven otras funciones
Una función también puede generar otra función. Te lo mostraremos a continuación con un ejemplo.
def hello_function():
def say_hi():
return "Hi"
return say_hi
hello = hello_function()
hello()
'Hi'
Las Funciones Anidadas tienen acceso al Ámbito de Variables de la Función Anexa
Python permite que una función anidada acceda al ámbito externo de la función que la encierra. Se trata de un concepto fundamental en los decoradores: este patrón se conoce como Cierre.
def print_message(message):
"Enclosong Function"
def message_sender():
"Nested Function"
print(message)
message_sender()
print_message("Some random message")
Some random message
Crear decoradores
Con estos requisitos previos fuera del camino, vamos a crear un sencillo decorador que convierta una frase a mayúsculas. Lo hacemos definiendo una envoltura dentro de una función adjunta. Como puedes ver es muy similar a la función dentro de otra función que hemos creado antes.
def uppercase_decorator(function):
def wrapper():
func = function()
make_uppercase = func.upper()
return make_uppercase
return wrapper
Nuestra función decoradora toma una función como argumento, por lo que definiremos una función y se la pasaremos a nuestro decorador. Antes aprendimos que podíamos asignar una función a una variable. Utilizaremos ese truco para llamar a nuestra función decoradora.
def say_hi():
return 'hello there'
decorate = uppercase_decorator(say_hi)
decorate()
'HELLO THERE'
Sin embargo, Python nos proporciona una forma mucho más sencilla de aplicar decoradores. Simplemente utilizamos el símbolo @ delante de la función que queremos decorar. Vamos a demostrarlo en la práctica a continuación.
@uppercase_decorator
def say_hi():
return 'hello there'
say_hi()
'HELLO THERE'
Aplicar varios decoradores a una misma función
Podemos utilizar varios decoradores para una misma función. Sin embargo, los decoradores se aplicarán en el orden en que los hayamos llamado. A continuación definiremos otro decorador que divide la frase en una lista. A continuación, aplicaremos el decorador uppercase_decorator
y split_string
a una única función.
import functools
def split_string(function):
@functools.wraps(function)
def wrapper():
func = function()
splitted_string = func.split()
return splitted_string
return wrapper
@split_string
@uppercase_decorator
def say_hi():
return 'hello there'
say_hi()
['HELLO', 'THERE']
De la salida anterior, observamos que la aplicación de los decoradores es de abajo arriba. Si hubiéramos intercambiado el orden, habríamos visto un error, ya que las listas no tienen el atributo upper
. Primero se ha convertido la frase a mayúsculas y luego se ha dividido en una lista.
Nota: Al apilar decoradores, es una práctica habitual utilizar functools.wraps
para garantizar que los metadatos de la función original se conservan durante todo el proceso de apilamiento. Esto ayuda a mantener la claridad y la coherencia a la hora de depurar y comprender las propiedades de la función decorada.
Aceptar argumentos en funciones de decorador
A veces podemos necesitar definir un decorador que acepte argumentos. Lo conseguimos pasando los argumentos a la función envoltorio. Los argumentos se pasarán a la función que se esté decorando en el momento de la llamada.
def decorator_with_arguments(function):
def wrapper_accepting_arguments(arg1, arg2):
print("My arguments are: {0}, {1}".format(arg1,arg2))
function(arg1, arg2)
return wrapper_accepting_arguments
@decorator_with_arguments
def cities(city_one, city_two):
print("Cities I love are {0} and {1}".format(city_one, city_two))
cities("Nairobi", "Accra")
My arguments are: Nairobi, Accra
Cities I love are Nairobi and Accra
Nota: Es esencial asegurarse de que el número de argumentos del decorador (arg1, arg2
en este ejemplo) coincide con el número de argumentos de la función envuelta (cities
en este ejemplo). Esta alineación es crucial para evitar errores y garantizar una funcionalidad adecuada al utilizar decoradores con argumentos.
Definición de decoradores de uso general
Para definir un decorador de propósito general que pueda aplicarse a cualquier función, utilizamos args
y **kwargs
. args
y **kwargs
recogen todos los argumentos posicionales y de palabra clave y los almacenan en las variables args y kwargs. args
y kwargs
nos permiten pasar tantos argumentos como queramos durante las llamadas a funciones.
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
def a_wrapper_accepting_arbitrary_arguments(*args,**kwargs):
print('The positional arguments are', args)
print('The keyword arguments are', kwargs)
function_to_decorate(*args)
return a_wrapper_accepting_arbitrary_arguments
@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
print("No arguments here.")
function_with_no_argument()
The positional arguments are ()
The keyword arguments are {}
No arguments here.
Veamos cómo utilizaríamos el decorador utilizando argumentos posicionales.
@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
print(a, b, c)
function_with_arguments(1,2,3)
The positional arguments are (1, 2, 3)
The keyword arguments are {}
1 2 3
Los argumentos de las palabras clave se pasan utilizando palabras clave. A continuación se muestra un ejemplo.
@a_decorator_passing_arbitrary_arguments
def function_with_keyword_arguments():
print("This has shown keyword arguments")
function_with_keyword_arguments(first_name="Derrick", last_name="Mwiti")
The positional arguments are ()
The keyword arguments are {'first_name': 'Derrick', 'last_name': 'Mwiti'}
This has shown keyword arguments
Nota: El uso de **kwargs
en el decorador le permite manejar argumentos de palabras clave. Esto hace que el decorador de propósito general sea versátil y capaz de manejar diversos tipos de argumentos durante las llamadas a funciones.
Pasar argumentos al decorador
Veamos ahora cómo pasar argumentos al propio decorador. Para ello, definimos un creador de decoradores que acepte argumentos y, a continuación, definimos un decorador dentro de él. A continuación, definimos una función envolvente dentro del decorador, como hicimos anteriormente.
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):
def decorator(func):
def wrapper(function_arg1, function_arg2, function_arg3) :
"This is the wrapper function"
print("The wrapper can access all the variables\n"
"\t- from the decorator maker: {0} {1} {2}\n"
"\t- from the function call: {3} {4} {5}\n"
"and pass them to the decorated function"
.format(decorator_arg1, decorator_arg2,decorator_arg3,
function_arg1, function_arg2,function_arg3))
return func(function_arg1, function_arg2,function_arg3)
return wrapper
return decorator
pandas = "Pandas"
@decorator_maker_with_arguments(pandas, "Numpy","Scikit-learn")
def decorated_function_with_arguments(function_arg1, function_arg2,function_arg3):
print("This is the decorated function and it only knows about its arguments: {0}"
" {1}" " {2}".format(function_arg1, function_arg2,function_arg3))
decorated_function_with_arguments(pandas, "Science", "Tools")
The wrapper can access all the variables
- from the decorator maker: Pandas Numpy Scikit-learn
- from the function call: Pandas Science Tools
and pass them to the decorated function
This is the decorated function, and it only knows about its arguments: Pandas Science Tools
Depuración de decoradores
Como hemos visto, los decoradores envuelven funciones. El nombre de la función original, su docstring y la lista de parámetros quedan ocultos por el cierre envolvente: Por ejemplo, cuando intentemos acceder a los metadatos de decorated_function_with_arguments
, veremos los metadatos del cierre envoltorio. Esto supone un reto a la hora de depurar.
decorated_function_with_arguments.__name__
'wrapper'
decorated_function_with_arguments.__doc__
'This is the wrapper function'
Para resolver este problema, Python proporciona un decorador functools.wraps
. Este decorador copia los metadatos perdidos de la función no decorada al cierre decorado. Vamos a mostrar cómo lo haríamos.
import functools
def uppercase_decorator(func):
@functools.wraps(func)
def wrapper():
return func().upper()
return wrapper
@uppercase_decorator
def say_hi():
"This will say hi"
return 'hello there'
say_hi()
'HELLO THERE'
Cuando comprobamos los metadatos de say_hi
, observamos que ahora hace referencia a los metadatos de la función y no a los del envoltorio.
say_hi.__name__
'say_hi'
say_hi.__doc__
'This will say hi'
Es aconsejable y una buena práctica utilizar siempre functools.wraps
al definir los decoradores. Te ahorrará muchos quebraderos de cabeza en la depuración.
Resumen de los Decoradores de Python
Los decoradores modifican dinámicamente la funcionalidad de una función, método o clase sin tener que utilizar directamente subclases ni cambiar el código fuente de la función que se está decorando. Utilizar decoradores en Python también garantiza que tu código sea DRY(Don't Repeat Yourself). Los decoradores tienen varios casos de uso, como
- Autorización en frameworks de Python como Flask y Django
- Registro
- Medir el tiempo de ejecución
- Sincronización
Para saber más sobre los decoradores de Python, consulta la Biblioteca de Decoradores de Python.
Más información sobre Python
Course
Writing Efficient Python Code
Course
Introduction to Functions in Python
tutorial
Tutorial de funciones de Python
tutorial
Tutorial de cadenas en Python
tutorial
Tutorial de visualización de datos con Python y Tableau
tutorial
Python Seaborn Tutorial Para Principiantes: Empezar a visualizar datos
tutorial
Tutorial de list index() de Python
tutorial
Tutorial de Python String format()
DataCamp Team
5 min