É possível substituir um decorador de função / método em tempo de execução? [ Pitão ]
-
22-07-2019 - |
Pergunta
Se eu tiver uma função:
@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()
É possível substituir um decorador em tempo de execução?
Solução
Eu não sei se há uma maneira de "substituir" um decorador uma vez que foi aplicado, mas acho que, provavelmente, não há, porque a função já foi alterado.
Você pode, de qualquer forma, aplicar um decorador em tempo de execução com base em alguma condição:
#!/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)
Outras dicas
Como Miya mencionado, você pode substituir o decorador com outra função qualquer ponto antes de o intérprete começa a essa declaração função. No entanto, uma vez que o decorador é aplicado para a função, eu não acho que há uma maneira para substituir dinamicamente o decorador com um diferente. Assim, por exemplo:
@aDecorator
def myfunc1():
pass
# Oops! I didn't want that decorator after all!
myfunc1 = bDecorator(myfunc1)
não vai funcionar, porque MyFunc1 não é mais a função que originalmente definido; já foi embrulhado. A melhor abordagem aqui é para aplicar manualmente os decoradores, oldskool-style, ou seja:
def myfunc1():
pass
myfunc2 = aDecorator(myfunc1)
myfunc3 = bDecorator(myfunc1)
Edit: Ou, para ser um pouco mais claro,
def _tempFunc():
pass
myfunc1 = aDecorator(_tempFunc)
myfunc1()
myfunc1 = bDecorator(_tempFunc)
myfunc1()
Aqui está uma excelente receita para você começar . Basicamente, a idéia é passar uma instância de classe para o decorador. Você pode então definir atributos na instância de classe (torná-lo um Borg se quiser) e usar isso para controlar o comportamento do próprio decorador.
Aqui está um exemplo:
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, você pode também incorporar a "decorador decorador" para preservar assinaturas, etc.
Claro - você pode obter o objeto de função e fazer o que quiser com ele:
# 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__()
Se você quiser alterar explicitamente o decorador, assim como você pode escolher uma abordagem mais explícita em vez de criar uma função decorado:
deco1(myfunc1, arg1, arg2)
deco2(myfunc1, arg2, arg3)
deco1 () e Deco2 () seria aplicável a funcionalidade seus decoradores fornecer e MyFunc1 call () com os argumentos.
Eu sei que é uma discussão antiga, mas eu me diverti fazendo isso
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]
Se o decorador é uma função, simplesmente substituí-lo.
aDecorator = otherDecorator