¿Cómo se puede adjuntar un decorador a una función “después del hecho” en python?

La forma en que entiendo a los decoradores de la función en python (y podría estar equivocado), es que se supone que agregan efectos secundarios y modifican el valor de retorno de una función. Ahora los decoradores se agregan por encima de la definición de la función de la función a decorar o por una tarea. Aquí hay un pequeño ejemplo:

def print_args_decor(function): def wrapper(*args, **kwargs): print 'Arguments:', args, kwargs # Added side-effect return function(*args, **kwargs)*5 # Modified return value return wrapper @print_args_decor def do_stuff(strg, n=10): """Repeats strg a few times.""" return strg * n new_decorated_func = print_args_decor(do_stuff) # Decoration by assignment print do_stuff('a', 2) # Output: aaaaaaaaaa 

Ahora, ¿cómo se adjunta un decorador a una función definida en otro lugar , idealmente conservando el nombre de la función original y la cadena de documentos (como hace functools.wraps )? Por ejemplo, estoy importando la función sqrt() del módulo matemático de Python, y quiero decorarla, ¿cómo hago para eso?

 from functools import wraps from math import sqrt def print_args_decor(function): @wraps(function) def wrapper(*args, **kwargs): print 'Arguments:', args, kwargs # Added side-effect return function(*args, **kwargs)*5 # Modified return value return wrapper # Decorate the sqrt() function from math module somehow @print_args_decor #??? sqrt #??? print sqrt(9) # Output: # Arguments: ([9],) {} # 15 # <--- sqrt(9)*5 

¿Qué hay de decorar los métodos dentro de las clases después del hecho? ¿Qué tal las clases de decoración?

Importó sqrt en su módulo, simplemente aplique el decorador allí en su propio espacio de nombres global:

 sqrt = print_args_decor(sqrt) 

Esto establece el nombre sqrt en el espacio de nombres de su módulo al resultado del decorador. No hay ningún requisito de que sqrt se haya definido originalmente en este módulo.

Es functools.wraps() del decorador utilizar el decorador functools.wraps() para conservar los metadatos de la función, como el nombre y la cadena de documentación.

Decorar una clase no es diferente a este respecto:

 ClassName = decorator(ClassName) 

En Python 2, para los métodos debe tener cuidado de tomar la función original no vinculada; Lo más fácil es usar el method.__func__ atributo:

 try: # Python 2 ClassName.function_name = decorator(ClassName.function_name.__func__) except AttributeError: # Python 3 ClassName.function_name = decorator(ClassName.function_name) 

He envuelto lo anterior en un try...except para hacer que el patrón funcione en las versiones de Python. La alternativa es tomar el objeto de función de la clase __dict__ para evitar que el protocolo descriptor __dict__ :

 ClassName.function_name = decorator(ClassName.__dict__['function_name'])