Decoradores Python: una guía completa

Un decorador es una herramienta de patrón de diseño en Python para envolver código alrededor de funciones o clases (bloques definidos). Este patrón de diseño permite a un programador agregar nueva funcionalidad a funciones o clases existentes sin modificar la estructura existente. La sección proporciona una descripción general de qué son los decoradores, cómo decorar funciones y clases, y qué problema puede resolver.

Decorators

Decoradores

Comprender a los decoradores

Un decorador es una función que toma otra función como argumento, realiza algunas acciones y luego devuelve el argumento en función de las acciones realizadas. Dado que las funciones son objetos de primera clase en Python, se pueden pasar como argumentos a otras funciones.
Por lo tanto, podemos decir que un decorador es un invocable que acepta y devuelve un invocable.
El siguiente código muestra un decorador simple que agrega notas adicionales a la string de documentación my_function :

def decorated_docstring(function):
    function.__doc__ += '\n Hi George, I''m a simple Decorator.'
    return function
  
def my_function(string1):
    """Return the string."""
    return string1
  
def main():
    myFunc = decorated_docstring(my_function)
    myFunc('Hai')
    help(myFunc)
  
if __name__ == "__main__":
    main()

Producción:

Help on function my_function in module __main__:

my_function(string1)
    Return the string.
    Hi George, Im a simple Decorator.

Sintaxis del decorador

De la explicación anterior, puedes entender cómo decorar una función. Pero es inaceptable definir una función, asignarla a una variable y luego reasignar la función decorada a la misma variable. Por lo tanto, Python 2.5 introdujo una sintaxis para la función de decoración al anteponer un @carácter con el nombre del decorador y agregarlo directamente encima de la declaración de la función. El siguiente código muestra cómo usar la sintaxis:

def reverse_decorator(function):
  
    def reverse_wrapper():
        make_reverse = "".join(reversed(function()))
        return make_reverse
  
    return reverse_wrapper
  
@reverse_decorator
def say_hi():
    return 'Hi George'
  
def main():
    print(say_hi())
  
if __name__ == "__main__":
    main()

Producción:

egroeG iH

Aquí usamos el carácter @ para la función de decoración.

@reverse_decorator
def say_hi():
    return 'Hi George'

El código anterior es sintácticamente equivalente al siguiente códigoreverse_decorator(say_hi())

En este caso, la función reverse_decorator se ejecuta y crea la referencia para revserse_wrapper. Veamos el siguiente ejemplo para una mejor comprensión:

def reverse_decorator(function):
    print('Inside reverse_decorator function')
  
    def reverse_wrapper():
        print('Inside reverse_wrapper function')
        return 'Return reverse_wrapper function'
  
    return reverse_wrapper
  
  
@reverse_decorator
def say_hi():
    return 'Inside say_hi'

Producción:

Inside reverse_decorator function

Aquí, reverse_decorator no ejecuta la función reverse_wrapper, sino que crea la referencia y la devuelve cuando el invocable invoca la función.

def reverse_decorator(function):
    print('Inside reverse_decorator function')
    
    def reverse_wrapper():
        print('Inside reverse_wrapper function')
        return 'reverse_wrapper'
  
    return reverse_wrapper
  
  
@reverse_decorator
def say_hi():
    return 'Inside say_hi'
  
      
def main():
    print('Inside main()')
    print(say_hi)
    print(''); 
    print(say_hi()) # main executes the reference
  
main()

Producción:

Dentro de la función reverse_decorator
Dentro de main()
<función reverse_decorator..reverse_wrapper en 0x0000015762A16708>

Dentro de la función
reverse_wrapper reverse_wrapper

Función única y decorador múltiple

Ahora tienes claro cómo usar la @syntax para decorar una función. Otra cosa interesante es que podemos usar múltiples decoradores en una sola función. Una cosa importante a tener en cuenta aquí es el orden del decorador aplicado, que se aplica de abajo hacia arriba . Veamos varios decoradores.

def reverse_decorator(function):
  
    def reverse_wrapper():
        make_reverse = "".join(reversed(function()))
        return make_reverse
  
    return reverse_wrapper
  
def uppercase_decorator(function):
  
    def uppercase_wrapper():
        var_uppercase = function().upper()
        return var_uppercase
  
    return uppercase_wrapper
  
@uppercase_decorator
@reverse_decorator
def say_hi():
    return 'hi george'
  
def main():
    print(say_hi())
  
if __name__ == "__main__":
    main()

Producción:

EGROEG IH

Aquí, la string se invierte en primer lugar y, en segundo lugar, se convierte a mayúsculas.

@uppercase_decorator
@reverse_decorator
def say_hi():
    return 'hi george'

El código anterior es sintácticamente equivalente a
uppercase_decorator(reverse_decorator(say_hi()))

Argumentos en la función de decorador

Hasta ahora, has visto decoradores sin ningún argumento. En ciertos casos, es necesario pasar argumentos para decorar el método en consecuencia.

def decorator_arguments(function):
    def wrapper_arguments(arg1, arg2):
        print("Arguments accepted are: {0}, {1}".format(arg1, arg2))
        function(arg1, arg2)  # calls the function hobbies
  
    return wrapper_arguments
  
@decorator_arguments
def hobbies(hobby_one, hobby_two):
    print("My Hobbies are {0} and {1}".format(hobby_one, hobby_two))
  
def main():
    hobbies("Travelling", "Reading")
  
if __name__ == "__main__":
    main()

Producción:

Los argumentos aceptados son: Viajar, Leer
Mis pasatiempos son Viajar y Leer

En el caso anterior, el decorador no tomará ningún argumento, sino que el invocable lo pasará a la función contenedora. El siguiente código proporciona más claridad en el proceso de pasar los argumentos mediante referencia.

def decorator_arguments(function):
  
    def wrapper_arguments(arg1, arg2):
        print(" Arguments accepted are: {0}, {1}".format(arg1, arg2))
  
    return wrapper_arguments
  
@decorator_arguments
def hobbies(hobby_one, hobby_two):
    print("My Hobbies are {0} and {1}".format(hobby_one, hobby_two))
  
def main():
  
    # the reference of wrapper_arguments
    # is returned
    hob = hobbies
    print(hob)
  
    # passing the arguments to the 
    # wrapper_arguments
    hob('Travelling', 'Reading')
  
main()

Producción

<function decorator_arguments..wrapper_arguments at 0x7f100315e158> Los
argumentos aceptados son: viajar, leer

¿Dónde se utilizan los decoradores?

Where Decorators are used
The standard library provides many modules that include decorators and many Python tools and frameworks that make use of decorators. Few examples are:

  • Puede usar el decorador @classmethod o @staticmethod para crear un método sin crear una instancia
  • El módulo simulado permite el uso de @mock.patch o @mock.patch.object como decorador que se usa para pruebas unitarias.
  • Las herramientas comunes como Django (usa @login_required) para configurar los privilegios de inicio de sesión y Flask (usa @app.route) para el registro de funciones usa decoradores.
  • Para identificar una función como una tarea asíncrona, Celery usa el decorador @task.

Por qué deberías escribir Decoradores

Why Decorators
Two important reasons to write this reusable piece of Python functionality are when written properly the decorators are modular and explicit.

  • Modularidad de Decoradores
  • Los decoradores son explícitos

Modularidad de Decoradores

El uso de programadores de decoradores puede agregar o eliminar funcionalidades fácilmente en bloques de código definidos, lo que evita la repetición de la configuración repetitiva.

Los decoradores son explícitos

Los programadores pueden aplicar decoradores a todos los llamables según la necesidad. Esto asegura la legibilidad y proporciona un código limpio.

Casos de uso del decorador

When to write Decorators
Here we will discuss a few decorator use-cases which help you to understand When to write Decorators.

  1. Complementos funcionales
  2. Sanitización de datos
  3. Registro de función

Complementos funcionales

La primera y principal razón para escribir un decorador es su flexibilidad para agregar funcionalidad adicional a los bloques de código definidos (funciones y clases).

Sanitización de datos

Con los métodos adecuados de desinfección de datos, puede eliminar o destruir los datos almacenados en la memoria y hacer que sea imposible recuperarlos. Por ejemplo, puede usar la función de caché para evitar ejecutar la misma función o puede usar métodos para validar las credenciales de inicio de sesión del usuario, etc., lo que indica la importancia de la desinfección de datos. Un decorador puede desinfectar adecuadamente los argumentos que pasaron a la función decorada y también desinfectar los datos devueltos por la función.

Registro de función

Otro punto para escribir un decorador es registrar una función. Aquí el decorador permite que dos o más subsistemas se comuniquen sin tener mucha información entre sí.

Clases de decoración

La sección anterior muestra cómo los decoradores ayudan en las funciones de decoración. Hemos visto que los decoradores son invocables que aceptan y devuelven invocables. Dado que las clases son invocables, los decoradores también se utilizan para decorar clases. Algunos de los usos de las clases de decoración son:

  • Adición o mejora de atributos
  • Interacción de atributos
  • Alterar la API de una clase (alterar la forma de declarar clases y su uso de instancia)

Veamos cómo decorar una función usando class.

class MyDecorator:
  
    def __init__(self, function):
        self.function = function
  
    def __call__(self):
        print("Inside Function Call")
        self.function()
  
@MyDecorator
def function():
    print("GeeksforGeeks")
  
def main():
    function()
  
if __name__ == "__main__":
    main()

Producción:

Inside Function Call
GeeksforGeeks

Veamos otro ejemplo. Debajo del código, ordene la instancia según su tiempo de creación. Aquí requerimos tres atributos adicionales: la marca de tiempo de creación de instancias, los métodos __lt__ y __gt__.

import functools
import time
  
def sort_by_creation_time(cl):
    org_init = cl.__init__
  
    # Enhance the class to store the creation
    # time based on the instantiation.
    @functools.wraps(org_init)
    def new_init(self, *args, **kwargs):
        org_init(self, *args, **kwargs)
        self._created = time.time()
  
    # __lt__ and __gt__ methods return true or false 
    # based on the creation time.
    cl.__init__ = new_init
    cl.__lt = lambda self, other: self._created < other._created
    cl.__gt__ = lambda self, other: self._created > other._created
    return cl
  
@sort_by_creation_time
class Sort(object):
    def __init__(self, identifier):
        self.identifier = identifier
  
    def __repr__(self):
        return self.identifier
  
  
def main():
  
    first = Sort('Python')
    second = Sort('Data Analysis')
    third = Sort('Machine Learning')
  
    sortables = [third, first, second]
    print(sorted(sortables))
  
if __name__ == "__main__":
    main()

Producción

[Python, Data Analysis, Machine Learning]

new_init, su principal responsabilidad es ejecutar la función envuelta y también agregar una funcionalidad adicional a la función envuelta . La @functools.wraps(org_init)actualización de la función contenedora para reflejar el aspecto de la función envuelta. Verifique las funciones para comprender los detalles.

@functools.wraps(org_init)
def new_init(self, *args, **kwargs):
 
    # calls the init method of class
    org_init(self, *args, **kwargs)
 
    # add an extra attribute and store 
    # creation time  
    self._created = time.time() 
 
# reference of new_init is assigned to
# __init__ method and executes when
# callable creates the class object.
cl.__init__ = new_init  

Decora una función y devuelve una clase

A veces es necesario decorar una función y devolver una clase. Digamos, por ejemplo, que los desarrolladores de casos avanzados pueden subclasificar una clase en una API. También puede evitar un aumento en el código repetitivo.

class Addition(object):
  
    def __call__(self, *args, **kwargs):
        return self.run(*args, **kwargs)
  
    def run(self, *args, **kwargs):
        raise NotImplementedError('Subclass must implement `run`.')
          
    def identity(self):
        return 'Hi George, I''m here to help you with addition !'
  
def addition(decorated):
    class AddSubclass(Addition):
        def run(self, *args, **kwargs):
  
            if args:
                add = 0
                for arg in args:
                    add = arg + add   # add arguments 
                return add
                  
            else:
                # func is called if there are no arguments passed.
                return decorated(*args, **kwargs) 
                                                      
  
    return AddSubclass()
  
@addition
def func(*args):
    return 1 + 1
  
print(func.identity())
print(func())
print(func(2, 2))
print(func(2, 1, 4))

Producción

Hi George, Im here to help you with addition!
2
4
7

Aquí el decorador crea una subclase de Adición y devuelve la instancia de la subclase en lugar de la referencia ( return AddSubclass()).

def addition(decorated):
    class AddSubclass(Addition):
        def run(self, *args, **kwargs):
  
            if args:
                add = 0
                for arg in args:
                    add = arg + add
                return add
                  
            else:
                return decorated(*args, **kwargs)
  
    return AddSubclass()

Y el método __call__ en la clase `Addition` significa que su instancia es invocable. Por lo tanto, la instancia de la subclase `AddSubclass()` puede ejecutar la tarea y, como resultado, el programador puede evitar llamar al método de ejecución como func().run().

Resumen

Los decoradores son una excelente herramienta para envolver código alrededor de funciones y clases, lo que proporciona una manera eficiente de usar código repetitivo y también contribuye a la legibilidad. Aunque son modulares y explícitos, esta herramienta tiene inconvenientes. Dado que es una herramienta para envolver código alrededor de bloques definidos, la depuración se vuelve más desafiante. Y también, los decoradores mal escritos pueden resultar en un error.
Después de todo, un decorador bien escrito puede tratarse como una pieza eficiente y reutilizable de la funcionalidad de Python para envolver bloques de código definidos.

Publicación traducida automáticamente

Artículo escrito por SonuGeorge y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *