Est-il possible de remplacer un décorateur de fonctions / méthodes au moment de l'exécution? [python]

StackOverflow https://stackoverflow.com/questions/642762

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?

Était-ce utile?

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top