Saltar al contenido principal
InicioTutorialesPython

Tutorial de Decoradores Python

En este tutorial aprenderás a implementar decoradores en Python.
Actualizado 11 sept 2024  · 7 min leer

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

Domina Python para la ciencia de datos y adquiere habilidades muy demandadas.
Empieza a Aprender Gratis

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.

Temas

Más información sobre Python

Course

Writing Functions in Python

4 hr
87.9K
Learn to use best practices to write maintainable, reusable, complex functions with good documentation.
See DetailsRight Arrow
Start Course
Ver másRight Arrow
Relacionado

tutorial

Tutorial de funciones de Python

Un tutorial sobre funciones en Python que cubre cómo escribir funciones, cómo invocarlas y mucho más.
Karlijn Willems's photo

Karlijn Willems

14 min

tutorial

Tutorial de cadenas en Python

En este tutorial, aprenderás todo sobre las cadenas de Python: trocearlas y encadenarlas, manipularlas y darles formato con la clase Formatter, cadenas f, plantillas y ¡mucho más!
Sejal Jaiswal's photo

Sejal Jaiswal

16 min

tutorial

Tutorial de visualización de datos con Python y Tableau

Aprende a utilizar Python para ampliar las funciones de visualización de datos de Tableau.
Abid Ali Awan's photo

Abid Ali Awan

15 min

tutorial

Python Seaborn Tutorial Para Principiantes: Empezar a visualizar datos

Este tutorial de Seaborn le introduce en los fundamentos de la visualización de datos estadísticos
Moez Ali's photo

Moez Ali

20 min

tutorial

Tutorial de list index() de Python

En este tutorial, aprenderás exclusivamente sobre la función index().
Sejal Jaiswal's photo

Sejal Jaiswal

6 min

tutorial

Tutorial de Python String format()

Aprende a formatear cadenas en Python.
DataCamp Team's photo

DataCamp Team

5 min

See MoreSee More