Est-il possible de remplacer un décorateur de fonctions / méthodes au moment de l'exécution? [python]
-
22-07-2019 - |
Question
Si j'ai une fonction:
@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()
Est-il possible de remplacer un décorateur au moment de l'exécution?
La solution
Je ne sais pas s'il existe un moyen de "remplacer" un décorateur une fois appliqué, mais j'imagine que ce n’est probablement pas le cas, car la fonction a déjà été modifiée.
Vous pouvez néanmoins appliquer un décorateur au moment de l’exécution en fonction de certaines conditions:
#!/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)
Autres conseils
Comme Miya l’a mentionné, vous pouvez remplacer le décorateur par une autre fonction à tout moment avant que l’interprète n’atteigne cette déclaration de fonction. Cependant, une fois que le décorateur est appliqué à la fonction, je ne pense pas qu'il soit possible de remplacer dynamiquement le décorateur par un autre. Ainsi, par exemple:
@aDecorator
def myfunc1():
pass
# Oops! I didn't want that decorator after all!
myfunc1 = bDecorator(myfunc1)
Ne fonctionnera pas car myfunc1 n'est plus la fonction que vous avez définie à l'origine; il a déjà été emballé. La meilleure approche consiste ici à appliquer manuellement les décorateurs, à l’ancienne, à savoir:
def myfunc1():
pass
myfunc2 = aDecorator(myfunc1)
myfunc3 = bDecorator(myfunc1)
Modifier: Ou, pour être un peu plus clair,
def _tempFunc():
pass
myfunc1 = aDecorator(_tempFunc)
myfunc1()
myfunc1 = bDecorator(_tempFunc)
myfunc1()
Voici une recette pour vous aider à démarrer . En gros, l’idée est de passer une instance de classe au décorateur. Vous pouvez ensuite définir des attributs sur l’instance de la classe (en faire un Borg si vous le souhaitez) et l’utiliser pour contrôler le comportement du décorateur lui-même.
Voici un exemple:
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')
Naturellement, vous pouvez également incorporer le "décorateur décorateur". préserver les signatures, etc.
Bien sûr - vous pouvez obtenir l'objet fonction et en faire ce que vous voulez:
# 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 vous souhaitez modifier explicitement le décorateur, vous pouvez également choisir une approche plus explicite au lieu de créer une fonction décorée:
deco1(myfunc1, arg1, arg2)
deco2(myfunc1, arg2, arg3)
deco1 () et deco2 () appliqueraient les fonctionnalités fournies par vos décorateurs et appelleraient myfunc1 () avec les arguments.
Je sais que c'est un vieux fil, mais je me suis amusé à le faire
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 le décorateur est une fonction, remplacez-la simplement.
aDecorator = otherDecorator