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.