Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking whether a function is decorated

I am trying to build a control structure in a class method that takes a function as input and has different behaviors if a function is decorated or not. Any ideas on how you would go about building a function is_decorated that behaves as follows:

def dec(fun):
    # do decoration


def func(data):
    # do stuff


@dec
def func2(data):
    # do other stuff


def is_decorated(func):
    # return True if func has decorator, otherwise False


is_decorated(func)  # False
is_decorated(func2) # True

2 Answers

Yes, it's relatively easy because functions can have arbitrary attributes added to them, so the decorator function can add one when it does its thing:

def dec(fun):
    def wrapped(*args, **kwargs):
        pass

    wrapped.i_am_wrapped = True
    return wrapped

def func(data):
    ... # do stuff

@dec
def func2(data):
    ... # do other stuff

def is_decorated(func):
    return getattr(func, 'i_am_wrapped', False)


print(is_decorated(func))   # -> False
print(is_decorated(func2))  # -> True
like image 106
martineau Avatar answered Jul 26 '26 16:07

martineau


There are several ways to identify if a function is wrapped/decorated.

TLDR Solution

def is_decorated(func):
    return hasattr(func, '__wrapped__') or func.__name__ not in globals()

Scenario A

If we assume the decorator uses the helper decorator functools.wraps, then it is pretty straight forward, since our decorated function is going to have the attribute __wrapped__ after.

from functools import wraps


def decorator(function):
    @wraps(function)
    def _wrapper(*a, **kw): ...
    return _wrapper


@decorator
def foo(x, y): ...


def is_decorated(function):
    return hasattr(function, '__wrapped__')

Scenario B

In case we don't use wraps during the decorator's definition, then it's a bit different.

def decorator(function):
    def _wrapper(*a, **kw): ...
    return _wrapper


@decorator
def foo(x, y): ...


def bar(x, y): ...


print(bar.__name__)  # prints 'bar'
print(foo.__name__)  # prints '_wrapper' instead of 'foo'


def is_decorated(function):
    """
    globals() returns a dictionary which includes
    defined functions, classes and many other stuff.
    Assuming we haven't defined the inner/wrapper name
    as an outer function elsewhere, then it will not be
    in globals()
    """
    return function.__name__ not in globals()

Conclusion

There are other - more sophisticated - ways to check this, however readability is as important as completeness is, and this solution is as simple it can be. As mentioned in the previous code block, the only "hole" of this solution is the following situation:

from functools import wraps

def is_decorated(func):
    return hasattr(func, '__wrapped__') or func.__name__ not in globals()


def decorator_wraps(function):
    @wraps(function)
    def _wrapper(*a, **kw): ...
    return _wrapper


def decorator_no_wraps(function):
    def _wrapper(*a, **kw): ...
    return _wrapper


@decorator_wraps
def foo(x, y): ...


@decorator_no_wraps
def bar(x, y): ...


def baz(x, y): ...


for function in (foo, bar, baz):
    print(is_decorated(function))  # True, True, False


def _wrapper(): ...


for function in (foo, bar, baz):
    print(is_decorated(function))  # True, False, False
like image 28
Will Be Avatar answered Jul 26 '26 15:07

Will Be