sobrecarga qué parametros metodos ejemplo decoradores decorador con python decorator

parametros - qué es un decorador en python



Cómo despojar a los decoradores de una función en Python (10)

Añadir un decorador de no hacer nada:

def do_nothing(f): return f

Después de definir o importar with_connection pero antes de llegar a los métodos que lo utilizan como decorador, agregue:

if TESTING: with_connection = do_nothing

Luego, si establece la PRUEBA global en Verdadero, habrá reemplazado with_connection con un decorador de no hacer nada.

Digamos que tengo lo siguiente:

def with_connection(f): def decorated(*args, **kwargs): f(get_connection(...), *args, **kwargs) return decorated @with_connection def spam(connection): # Do something

Quiero probar la función de spam sin pasar por la molestia de configurar una conexión (o lo que sea que esté haciendo el decorador).

Dado el spam , ¿cómo puedo quitarle el decorador y obtener la función subyacente "sin decoración"?


Ahora puedes usar el paquete undecorated :

>>> from undecorated import undecorated >>> undecorated(spam)

Pasa por la molestia de cavar a través de todas las capas de diferentes decoradores hasta que alcanza la función inferior y no es necesario cambiar los decoradores originales. Funciona tanto en Python 2 como en Python 3.


El enfoque habitual para probar dichas funciones es hacer que las dependencias, como get_connection, sean configurables. Luego puedes anularlo con un simulacro durante la prueba. Básicamente, lo mismo que la inyección de dependencia en el mundo Java, pero mucho más simple gracias a la naturaleza dinámica de Pythons.

El código para ello podría ser algo como esto:

# decorator definition def with_connection(f): def decorated(*args, **kwargs): f(with_connection.connection_getter(), *args, **kwargs) return decorated # normal configuration with_connection.connection_getter = lambda: get_connection(...) # inside testsuite setup override it with_connection.connection_getter = lambda: "a mock connection"

Dependiendo de su código, puede encontrar un objeto mejor que el decorador para pegar la función de fábrica. El problema de tenerlo en el decorador es que deberías recordar restaurar el valor anterior en el método de desmontaje.


En el caso general, no se puede, porque

@with_connection def spam(connection): # Do something

es equivalente a

def spam(connection): # Do something spam = with_connection(spam)

lo que significa que el spam "original" podría no existir más. Un hack (no muy bonito) sería este:

def with_connection(f): def decorated(*args, **kwargs): f(get_connection(...), *args, **kwargs) decorated._original = f return decorated @with_connection def spam(connection): # Do something spam._original(testcon) # calls the undecorated function


En lugar de hacer ...

def with_connection(f): def decorated(*args, **kwargs): f(get_connection(...), *args, **kwargs) return decorated @with_connection def spam(connection): # Do something orig_spam = magic_hack_of_a_function(spam)

Podrías hacer ...

def with_connection(f): ... def spam_f(connection): ... spam = with_connection(spam_f)

... que es todo lo que @decorator sintaxis de @decorator - entonces, obviamente, puede acceder al spam_f original normalmente.


Es una buena práctica decorar decoradores con functools.wraps así:

import functools def with_connection(f): @functools.wraps(f) def decorated(*args, **kwargs): f(get_connection(...), *args, **kwargs) return decorated @with_connection def spam(connection): # Do something

A partir de Python 3.2, esto agregará automáticamente un atributo __wrapped__ que le permite recuperar la función original, sin decorar:

>>> spam.__wrapped__ <function spam at 0x7fe4e6dfc048>

Sin embargo, en lugar de acceder manualmente al atributo __wrapped__ , es mejor usar inspect.unwrap :

>>> inspect.unwrap(spam) <function spam at 0x7fe4e6dfc048>


Ha habido un poco de actualización para esta pregunta. Si está usando Python 3, puede usar la propiedad __wrapped__ para decoradores de stdlib.

Aquí hay un ejemplo de Python Cookbook, tercera edición, sección 9.3 Desenvolviendo decoradores

>>> @somedecorator >>> def add(x, y): ... return x + y ... >>> orig_add = add.__wrapped__ >>> orig_add(3, 4) 7 >>>

Si está intentando desempaquetar una función del decorador personalizado, la función del decorador necesita usar la función de wraps funciones functools Consulte la discusión en Python Cookbook, tercera edición, sección 9.2 Preservar los metadatos de la función al escribir decoradores

>>> from functools import wraps >>> def somedecoarator(func): ... @wraps(func) ... def wrapper(*args, **kwargs): ... # decorator implementation here ... # ... ... return func(*args, kwargs) ... ... return wrapper


He aquí, FuglyHackThatWillWorkforYourExampleButICantPromiseAnythingElse:

orig_spam = spam.func_closure[0].cell_contents

Edición : Para funciones / métodos decorados más de una vez y con decoradores más complicados, puede intentar usar el siguiente código. Se basa en el hecho de que las funciones decoradas son __name__d de manera diferente a la función original.

def search_for_orig(decorated, orig_name): for obj in (c.cell_contents for c in decorated.__closure__): if hasattr(obj, "__name__") and obj.__name__ == orig_name: return obj if hasattr(obj, "__closure__") and obj.__closure__: found = search_for_orig(obj, orig_name) if found: return found return None >>> search_for_orig(spam, "spam") <function spam at 0x027ACD70>

Aunque no es a prueba de tontos. Fallará si el nombre de la función devuelta por un decorador es el mismo que el decorado. El orden de las comprobaciones de hasattr () también es heurístico, hay cadenas de decoración que devuelven resultados incorrectos en cualquier caso.


La solución de balpha se puede hacer más generalizable con este meta-decorador:

def include_original(dec): def meta_decorator(f): decorated = dec(f) decorated._original = f return decorated return meta_decorator

Luego, puede decorar sus decoradores con @include_original, y cada uno tendrá una versión comprobable (sin decoración) escondida dentro de ella.

@include_original def shout(f): def _(): string = f() return string.upper() return _ @shout def function(): return "hello world" >>> print function() HELLO_WORLD >>> print function._original() hello world


la función original se almacena en spam.__closure__[0].cell_contents .
El decorador utiliza el cierre para unir la función original con una capa adicional de funcionalidad. La función original debe almacenarse en una celda de cierre mantenida por una de las funciones en la estructura anidada del decorador.
Ejemplo:

>>> def add(f): ... def _decorator(*args, **kargs): ... print(''hello_world'') ... return f(*args, **kargs) ... return _decorator ... >>> @add ... def f(msg): ... print(''f ==>'', msg) ... >>> f(''alice'') hello_world f ==> alice >>> f.__closure__[0].cell_contents <function f at 0x7f5d205991e0> >>> f.__closure__[0].cell_contents(''alice'') f ==> alice

Este es el principio básico de undecorated , puede consultar el código fuente para obtener más detalles.