¿Es posible reemplazar un decorador de función / método en tiempo de ejecución? [pitón]
-
22-07-2019 - |
Pregunta
Si tengo una función:
@aDecorator
def myfunc1():
# do something here
if __name__ = "__main__":
# this will call the function and will use the decorator @aDecorator
myfunc1()
# now I want the @aDecorator to be replaced with the decorator @otherDecorator
# so that when this code executes, the function no longer goes through
# @aDecorator, but instead through @otherDecorator. How can I do this?
myfunc1()
¿Es posible reemplazar un decorador en tiempo de ejecución?
Solución
No sé si hay una manera de " reemplazar " un decorador una vez que se ha aplicado, pero supongo que probablemente no, porque la función ya ha cambiado.
De todos modos, puede aplicar un decorador en tiempo de ejecución en función de alguna condición:
#!/usr/bin/env python
class PrintCallInfo:
def __init__(self,f):
self.f = f
def __call__(self,*args,**kwargs):
print "-->",self.f.__name__,args,kwargs
r = self.f(*args,**kwargs)
print "<--",self.f.__name__,"returned: ",r
return r
# the condition to modify the function...
some_condition=True
def my_decorator(f):
if (some_condition): # modify the function
return PrintCallInfo(f)
else: # leave it as it is
return f
@my_decorator
def foo():
print "foo"
@my_decorator
def bar(s):
print "hello",s
return s
@my_decorator
def foobar(x=1,y=2):
print x,y
return x + y
foo()
bar("world")
foobar(y=5)
Otros consejos
Como mencionó Miya, puede reemplazar el decorador con otra función en cualquier momento antes de que el intérprete llegue a esa declaración de función. Sin embargo, una vez que el decorador se aplica a la función, no creo que haya una forma de reemplazar dinámicamente el decorador por uno diferente. Entonces, por ejemplo:
@aDecorator
def myfunc1():
pass
# Oops! I didn't want that decorator after all!
myfunc1 = bDecorator(myfunc1)
No funcionará, porque myfunc1 ya no es la función que definió originalmente; Ya ha sido envuelto. El mejor enfoque aquí es aplicar manualmente los decoradores, estilo oldskool, es decir:
def myfunc1():
pass
myfunc2 = aDecorator(myfunc1)
myfunc3 = bDecorator(myfunc1)
Editar: O, para ser un poco más claro,
def _tempFunc():
pass
myfunc1 = aDecorator(_tempFunc)
myfunc1()
myfunc1 = bDecorator(_tempFunc)
myfunc1()
Aquí hay una excelente receta para comenzar . Básicamente, la idea es pasar una instancia de clase al decorador. A continuación, puede establecer atributos en la instancia de clase (que sea un Borg si lo desea) y usar eso para controlar el comportamiento del decorador en sí.
Aquí hay un ejemplo:
class Foo:
def __init__(self, do_apply):
self.do_apply = do_apply
def dec(foo):
def wrap(f):
def func(*args, **kwargs):
if foo.do_apply:
# Do something!
pass
return f(*args, **kwargs)
return func
return wrap
foo = Foo(False)
@dec(foo)
def bar(x):
return x
bar('bar')
foo.do_apply = True
# Decorator now active!
bar('baz')
Naturalmente, también puede incorporar el decorador decorador para preservar firmas, etc.
Claro, puede obtener el objeto de función y hacer lo que quiera con él:
# Bypass a decorator
import types
class decorator_test(object):
def __init__(self, f):
self.f = f
def __call__(self):
print "In decorator ... entering: ", self.f.__name__
self.f()
print "In decorator ... exiting: ", self.f.__name__
@decorator_test
def func1():
print "inside func1()"
print "\nCalling func1 with decorator..."
func1()
print "\nBypassing decorator..."
for value in func1.__dict__.values():
if isinstance(value, types.FunctionType) and value.func_name == "func1":
value.__call__()
Si desea cambiar explícitamente el decorador, también podría elegir un enfoque más explícito en lugar de crear una función decorada:
deco1(myfunc1, arg1, arg2)
deco2(myfunc1, arg2, arg3)
deco1 () y deco2 () aplicarían la funcionalidad que proporcionan sus decoradores y llamarían a myfunc1 () con los argumentos.
Sé que es un hilo viejo, pero me divertí haciendo esto
def change_deco(name, deco, placeholder=' #'):
with open(name + '.py', 'r') as file:
lines = file.readlines()
for idx, string in enumerate(lines):
if placeholder in string and repr(placeholder) not in string:
lines[idx] = f' @{deco}\r\n'
exec(''.join(lines))
return locals()[name]
Si el decorador es una función, simplemente reemplácelo.
aDecorator = otherDecorator