Is it possible to replace a Python function/method decorator at runtime?

16,338

Solution 1

I don't know if there's a way to "replace" a decorator once it has been applied, but I guess that probably there's not, because the function has already been changed.

You might, anyway, apply a decorator at runtime based on some condition:

#!/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)

Solution 2

As Miya mentioned, you can replace the decorator with another function any point before the interpreter gets to that function declaration. However, once the decorator is applied to the function, I don't think there is a way to dynamically replace the decorator with a different one. So for example:

@aDecorator
def myfunc1():
    pass

# Oops! I didn't want that decorator after all!

myfunc1 = bDecorator(myfunc1)

Won't work, because myfunc1 is no longer the function you originally defined; it has already been wrapped. The best approach here is to manually apply the decorators, oldskool-style, i.e:

def myfunc1():
    pass

myfunc2 = aDecorator(myfunc1)
myfunc3 = bDecorator(myfunc1)

Edit: Or, to be a little clearer,

def _tempFunc():
    pass

myfunc1 = aDecorator(_tempFunc)
myfunc1()
myfunc1 = bDecorator(_tempFunc)
myfunc1()

Solution 3

Here's a terrific recipe to get you started. Basically, the idea is to pass a class instance into the decorator. You can then set attributes on the class instance (make it a Borg if you like) and use that to control the behavior of the decorator itself.

Here's an example:

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')

Naturally, you can also incorporate the "decorator decorator" to preserve signatures, etc.

Solution 4

If you want to explicitely change the decorator, you might as well choose a more explicit approach instead of creating a decorated function:

deco1(myfunc1, arg1, arg2)
deco2(myfunc1, arg2, arg3)

deco1() and deco2() would apply the functionality your decorators provide and call myfunc1() with the arguments.

Solution 5

Sure - you can get the function object and do whatever you want with it:

# 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__()
Share:
16,338
MarkD
Author by

MarkD

Updated on June 03, 2022

Comments

  • MarkD
    MarkD about 2 years

    If I have a function :

    
    @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()
    

    Is it possible to replace a decorator at runtime?

  • Admin
    Admin over 15 years
    But this will not change myfunc1().
  • MarkD
    MarkD over 15 years
    I tried using your code, the function still executes with the initial decorator.
  • MarkD
    MarkD over 15 years
    Yes, I could have used a function that takes a function as it's first param, my function as it's second param, followed by the arguments, and have a pretty generic way of doing it.I was interested however, if I can replace a decorator with my own.I would like to "play" with other classes's behaviour
  • aaronasterling
    aaronasterling over 13 years
    This assumes control over the initial decorator. Imagine the decorator def aDecorator(f): return lambda *args, **kwargs: "HAHAHAHAHAHAHA". In general, this isn't close to a valid approach. If you do happen to have control of the initial decorator, then there are cleaner, more idiomatic ways to do this.