Wednesday, April 13, 2011

Is it possible to replace a function/method decorator at runtime? [ python ]

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?

From stackoverflow
  • If the decorator is a function, just replace it.

    aDecorator = otherDecorator
    
    unbeknown : But this will not change myfunc1().
    Geo : I tried using your code, the function still executes with the initial decorator.
    miya : wow.. didn't expect this to happen.
  • 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.

    Geo : 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
  • 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()
    
    S.Lott : +1: aDecorate( func1 ) and bDecorate( func1 )
  • 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.

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

0 comments:

Post a Comment