Buscar..


Introducción

Las funciones de decorador son patrones de diseño de software. Alteran dinámicamente la funcionalidad de una función, método o clase sin tener que usar subclases directamente o cambiar el código fuente de la función decorada. Cuando se usan correctamente, los decoradores pueden convertirse en herramientas poderosas en el proceso de desarrollo. Este tema cubre la implementación y las aplicaciones de las funciones de decorador en Python.

Sintaxis

  • def decorator_function (f): pass # define un decorador llamado decorator_function

  • @decorator_function
    def decorated_function (): pass # la función ahora está envuelta (decorada por) decorator_function

  • decorated_function = decorator_function (decorated_function) # esto es equivalente a usar el azúcar sintáctico @decorator_function

Parámetros

Parámetro Detalles
F La función a decorar (envolver)

Función decoradora

Los decoradores aumentan el comportamiento de otras funciones o métodos. Cualquier función que tome una función como parámetro y devuelva una función aumentada puede usarse como decorador .

# This simplest decorator does nothing to the function being decorated. Such
# minimal decorators can occasionally be used as a kind of code markers.
def super_secret_function(f):
    return f

@super_secret_function
def my_function():
    print("This is my secret function.")

La notación @ es azúcar sintáctica que es equivalente a lo siguiente:

my_function = super_secret_function(my_function)

Es importante tener esto en cuenta para comprender cómo funcionan los decoradores. Esta sintaxis "no saturada" deja claro por qué la función decoradora toma una función como argumento y por qué debería devolver otra función. También demuestra lo que sucedería si no devuelves una función:

def disabled(f):
    """
    This function returns nothing, and hence removes the decorated function
    from the local scope.
    """
    pass

@disabled
def my_function():
    print("This function can no longer be called...")

my_function()
# TypeError: 'NoneType' object is not callable

Por lo tanto, generalmente definimos una nueva función dentro del decorador y la devolvemos. Esta nueva función primero haría algo que debe hacer, luego llama a la función original y, finalmente, procesa el valor de retorno. Considere esta función decoradora simple que imprime los argumentos que recibe la función original y luego la llama.

#This is the decorator
def print_args(func):
    def inner_func(*args, **kwargs):
        print(args)
        print(kwargs)
        return func(*args, **kwargs) #Call the original function with its arguments.
    return inner_func

@print_args
def multiply(num_a, num_b):
    return num_a * num_b
  
print(multiply(3, 5))
#Output:
# (3,5) - This is actually the 'args' that the function receives.
# {} - This is the 'kwargs', empty because we didn't specify keyword arguments.
# 15 - The result of the function.

Clase de decorador

Como se mencionó en la introducción, un decorador es una función que se puede aplicar a otra función para aumentar su comportamiento. El azúcar sintáctico es equivalente a lo siguiente: my_func = decorator(my_func) . Pero, ¿y si el decorator fuera una clase? La sintaxis aún funcionaría, excepto que ahora my_func se reemplaza con una instancia de la clase decorator . Si esta clase implementa el método mágico __call__() , entonces todavía sería posible usar my_func como si fuera una función:

class Decorator(object):
    """Simple decorator class."""

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('Before the function call.')
        res = self.func(*args, **kwargs)
        print('After the function call.')
        return res

@Decorator
def testfunc():
    print('Inside the function.')

testfunc()
# Before the function call.
# Inside the function.
# After the function call.

Tenga en cuenta que una función decorada con un decorador de clase ya no se considerará una "función" desde la perspectiva de la comprobación de tipo:

import types
isinstance(testfunc, types.FunctionType)
# False
type(testfunc)
# <class '__main__.Decorator'>

Métodos de decoración

Para los métodos de decoración debe definir un método __get__ adicional:

from types import MethodType

class Decorator(object):
    def __init__(self, func):
        self.func = func
        
    def __call__(self, *args, **kwargs):
        print('Inside the decorator.')
        return self.func(*args, **kwargs)
    
    def __get__(self, instance, cls):
        # Return a Method if it is called on an instance
        return self if instance is None else MethodType(self, instance)

class Test(object):
    @Decorator
    def __init__(self):
        pass
    
a = Test()

Dentro del decorador.

¡Advertencia!

Los decoradores de clase solo producen una instancia para una función específica, por lo que decorar un método con un decorador de clase compartirá el mismo decorador entre todas las instancias de esa clase:

from types import MethodType

class CountCallsDecorator(object):
    def __init__(self, func):
        self.func = func
        self.ncalls = 0    # Number of calls of this method
        
    def __call__(self, *args, **kwargs):
        self.ncalls += 1   # Increment the calls counter
        return self.func(*args, **kwargs)
    
    def __get__(self, instance, cls):
        return self if instance is None else MethodType(self, instance)

class Test(object):
    def __init__(self):
        pass
    
    @CountCallsDecorator
    def do_something(self):
        return 'something was done'
    
a = Test()
a.do_something()
a.do_something.ncalls   # 1
b = Test()
b.do_something()
b.do_something.ncalls   # 2

Hacer que un decorador se vea como la función decorada.

Los decoradores normalmente eliminan los metadatos de la función ya que no son lo mismo. Esto puede causar problemas cuando se utiliza la meta-programación para acceder dinámicamente a los metadatos de la función. Los metadatos también incluyen las cadenas de documentación de la función y su nombre. functools.wraps hace que la función decorada se vea como la función original al copiar varios atributos a la función de envoltura.

from functools import wraps

Los dos métodos de envolver a un decorador están logrando lo mismo al ocultar que la función original ha sido decorada. No hay razón para preferir la versión de la función a la versión de clase a menos que ya esté utilizando una sobre la otra.

Como una función

def decorator(func):
    # Copies the docstring, name, annotations and module to the decorator
    @wraps(func)
    def wrapped_func(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapped_func

@decorator
def test():
    pass

test.__name__

'prueba'

Como una clase

class Decorator(object):
    def __init__(self, func):
        # Copies name, module, annotations and docstring to the instance.
        self._wrapped = wraps(func)(self)
        
    def __call__(self, *args, **kwargs):
        return self._wrapped(*args, **kwargs)

@Decorator
def test():
    """Docstring of test."""
    pass

test.__doc__

'Docstring of test'.

Decorador con argumentos (decorador de fábrica).

Un decorador toma solo un argumento: la función a decorar. No hay forma de pasar otros argumentos.

Pero a menudo se desean argumentos adicionales. El truco es, entonces, hacer una función que tome argumentos arbitrarios y devuelva un decorador.

Funciones de decorador

def decoratorfactory(message):
    def decorator(func):
        def wrapped_func(*args, **kwargs):
            print('The decorator wants to tell you: {}'.format(message))
            return func(*args, **kwargs)
        return wrapped_func
    return decorator

@decoratorfactory('Hello World')
def test():
    pass

test()

El decorador quiere decirte: Hola Mundo.

Nota IMPORTANTE:

Con tales fábricas de decoradores debe llamar al decorador con un par de paréntesis:

@decoratorfactory # Without parentheses
def test():
    pass

test()

TypeError: decorator () falta 1 argumento posicional requerido: 'func'

Clases de decorador

def decoratorfactory(*decorator_args, **decorator_kwargs):
    
    class Decorator(object):
        def __init__(self, func):
            self.func = func

        def __call__(self, *args, **kwargs):
            print('Inside the decorator with arguments {}'.format(decorator_args))
            return self.func(*args, **kwargs)
        
    return Decorator

@decoratorfactory(10)
def test():
    pass

test()

Dentro del decorador con argumentos (10,)

Crea una clase de singleton con un decorador.

Un singleton es un patrón que restringe la creación de instancias de una clase a una instancia / objeto. Usando un decorador, podemos definir una clase como un singleton forzando a la clase a devolver una instancia existente de la clase o crear una nueva instancia (si no existe).

def singleton(cls):    
    instance = [None]
    def wrapper(*args, **kwargs):
        if instance[0] is None:
            instance[0] = cls(*args, **kwargs)
        return instance[0]

    return wrapper

Este decorador se puede agregar a cualquier declaración de clase y se asegurará de que se cree como máximo una instancia de la clase. Cualquier llamada posterior devolverá la instancia de clase ya existente.

@singleton
class SomeSingletonClass:
    x = 2
    def __init__(self):
        print("Created!")

instance = SomeSingletonClass()  # prints: Created!
instance = SomeSingletonClass()  # doesn't print anything
print(instance.x)                # 2

instance.x = 3
print(SomeSingletonClass().x)    # 3

Por lo tanto, no importa si se refiere a la instancia de clase a través de su variable local o si crea otra "instancia", siempre obtiene el mismo objeto.

Usando un decorador para cronometrar una función.

import time
def timer(func):
    def inner(*args, **kwargs):
        t1 = time.time()
        f = func(*args, **kwargs)
        t2 = time.time()
        print 'Runtime took {0} seconds'.format(t2-t1)
        return f
    return inner

@timer
def example_function():
    #do stuff


example_function()


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow