Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Make Decorators Optionally Turn On Or Off

I am asking, given a function with a decorator, is it possible to run the function without invoking the decorator call?

Given a function foo, is it possible to optionally Turn On or Off a decorator on it?

Given

@decorator
def foo():
    //do_somthing

Is it possible run foo with decorator Turned Off?

There may exist some function where you may wish to run it with or without the decorator. For example(and not a good one, since it involves efficient caching) turn off decorator based caching in a factorial(n) function.

My Question is Similar to this question Optionally use decorators on class methods. It discusses a good application of decorator switching ON/OFF (exposing as an api);

if I had to use a function, say goo and give the option to either run goo with or without a decorator, I tried a primitive, hackish way to achieve this optional decorator on/off switching functionality as following:

# this is the decorator class that executes the function `goo`
class deco(object):
    def __init__(self, attr):
        print "I am initialized"
        self.fn = None
        # some args you may wana pass
        self.attr = attr
        # lets say you want these attributes to persist
        self.cid = self.attr['cid']
        self.vid = 0

    def __call__(self, f):
        # the call executes and returns another inner wrapper
        def wrap(*args):
            # this executes main function - see closure 
            self.fn = f
            self.vid = args[0]
            self.closure(*args)
        return wrap

    def closure(self, *args):
        n = args[0]
        self.cid[n] = self.vid
        #goo = deco(fn, attr)
        print 'n',n
        # executes function `goo`
        self.fn(*args)


class gooClass(object):
    class that instantias and wraps `goo`around
    def __init__(self, attr, deco):
        '''
        @param:
              - attr: some mutable data structure
              - deco: True or False. Whether to run decorator or not
        '''
        self.attr = attr
        self.deco = deco

    def __call__(self, *args):
        if self.deco:
            # initiate deco class with passed args
            foo = deco(self.attr)
            # now pass the `goo` function to the wrapper inside foo.__class__.__call__
            foo = foo(self.goo)
            return foo(*args)
        else:
            # execute w/o decorator
            return self.goo(*args)                        

    def goo(self, n):
        # recursive goo
        if n>0:
            print 'in goo',n
            #print n
            # to recurse, I recreate the whole scene starting with the class 
            # because of this the args in `deco` Class init never persist
            too = gooClass(self.attr, self.deco)
            return too(n-1)
        else: return n


def Fn(n, decoBool):
    # this function is where to start running from
    attr = {}
    cid = [0]*(n+1)
    attr['cid'] = cid

    #following wud work too but defeat the purpose - have to define again! foo is goo actually
    #@deco(attr)
    #def foo(n):
    #    if n>0:
    #        print 'in foo',n
    #        #print n
    #        return foo(n-1)
    #    else: return n
    #return foo(n), attr
    # create the gooClass and execute `goo` method instance
    foo = gooClass(attr, decoBool)
    print foo(n)
    return foo



res = Fn(5, True)
print res.attr
print "="*10
res = Fn(5, False)
print res.attr

which outputs:

I am initialized
n 5
in goo 5
I am initialized
n 4
in goo 4
I am initialized
n 3
in goo 3
I am initialized
n 2
in goo 2
I am initialized
n 1
in goo 1
I am initialized
n 0
None
{'cid': [0, 1, 2, 3, 4, 5]}
==========
in goo 5
in goo 4
in goo 3
in goo 2
in goo 1
0
{'cid': [0, 0, 0, 0, 0, 0]}

which technically works, but I think it's a bootstrapped hack. not pythonic. And each time a new class gets created recursively.

The question stands and I couldn't find one relevant answer here so I created this, Is there a way to turn Decorators On/Off optionally?

like image 392
user2290820 Avatar asked Dec 11 '22 16:12

user2290820


1 Answers

Attach the undecorated function to the decorated one, as unwrapped say, before returning it from the decorator.

For example

def add42(fn):
    def wrap(i):
        return fn(i) + 42
    wrap.unwrapped = fn
    return wrap

@add42
def mult3(i):
    return i * 3

mult3(1) # 45
mult3.unwrapped(1) # 3

You can make this approach work for a classmethod (as requested in the comments) by changing add42 to something like this:

class add42: 
    def __init__(self, clsmethod): 
        self.classmethod = clsmethod 
    def __get__(self, instance, cls): 
        self.unwrapped = self.classmethod.__get__(cls) 
        return self 
    def __call__(self, *args): 
        return self.unwrapped(*args) + 42 

And use it:

class Foo:
    @add42 
    @classmethod 
    def mult3(cls, i): 
        return  i * 3

Foo.mult3(1) # 45
Foo.mult3.unwrapped(1) # 3
like image 145
Nick Matteo Avatar answered Dec 25 '22 02:12

Nick Matteo