23

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?

talonmies
  • 70,661
  • 34
  • 192
  • 269
Geo
  • 93,257
  • 117
  • 344
  • 520

7 Answers7

20

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()
DNS
  • 37,249
  • 18
  • 95
  • 132
5

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)
Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193
4

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.

zweiterlinde
  • 14,557
  • 2
  • 27
  • 32
2

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.

  • 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 – Geo Mar 13 '09 at 13:58
1

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__()
Freddie
  • 1,019
  • 10
  • 11
  • 1
    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. – aaronasterling Nov 16 '10 at 23:44
-1

I know it's an old thread, but I had fun doing this

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]
-3

If the decorator is a function, just replace it.

aDecorator = otherDecorator
miya
  • 1,059
  • 1
  • 11
  • 20