Search code examples
pythonpython-decorators

Is it possible to check if a function is decorated inside another function?


Is there any way to check inside function f1 in my example if calling a function (here decorated or not_decorated) has a specific decorator (in code @out)? Is such information passed to a function?

def out(fun):
    def inner(*args, **kwargs):
        fun(*args, **kwargs)
    return inner

@out
def decorated():
    f1()

def not_decorated():
    f1()

def f1():
    if is_decorated_by_out: # here I want to check it
        print('I am')
    else:
        print('I am not')

decorated()
not_decorated()

Expected output:

I am
I am not

Solution

  • To be clear, this is egregious hackery, so I don't recommend it, but since you've ruled out additional parameters, and f1 will be the same whether wrapped or not, you've left hacks as your only option. The solution is to add a local variable to the wrapper function for the sole purpose of being found by means of stack inspection:

    import inspect
    
    def out(fun):
        def inner(*args, **kwargs):
            __wrapped_by__ = out
            fun(*args, **kwargs)
        return inner
    
    def is_wrapped_by(func):
        try:
            return inspect.currentframe().f_back.f_back.f_back.f_locals.get('__wrapped_by__') is func
        except AttributeError:
            return False
    
    @out
    def decorated():
        f1()
    
    def not_decorated():
        f1()
    
    def f1():
        if is_wrapped_by(out):
            print('I am')
        else:
            print('I am not')
    
    decorated()
    not_decorated()
    

    Try it online!

    This assumes a specific degree of nesting (the manual back-tracking via f_back to account for is_wrapped_by itself, f1, decorated and finally to inner (from out). If you want to determine if out was involved anywhere in the call stack, make is_wrapped_by loop until the stack is exhausted:

    def is_wrapped_by(func):
        frame = None
        try:
            # Skip is_wrapped_by and caller 
            frame = inspect.currentframe().f_back.f_back
            while True:
                if frame.f_locals.get('__wrapped_by__') is func:
                    return True
                frame = frame.f_back
        except AttributeError:
            pass
        finally:
            # Leaving frame on the call stack can cause cycle involving locals
            # which delays cleanup until cycle collector runs;
            # explicitly break cycle to save yourself the headache
            del frame
        return False