3

I want to get list off all decorators used for function, samething which works like this

@a
@b(arg=1)
f():
    pass

get_decorators(f) == ['a', 'b'] 
# or
get_decorators(f) == [a, b] 
Bartłomiej Bartnicki
  • 1,067
  • 2
  • 14
  • 30

1 Answers1

0

Bring a carrot because we're going down a rabbit hole on this one...

This can be simple if the decorators are simple:

def a(func):
    try:
        func.decorators.append('a')
    except AttributeError:
        func.decorators = ['a']
    # decorator business

    return func

def b(arg):
    def _deco(func)
        try:
            func.decorators.append('b')
        except AttributeError:
            func.decorators = ['b']

        # decorator business

        return func
    return _deco

@a
@b(arg=1)
def my_func():
    pass

print(my_func.decorators)    # Go full python and put attributes on your functions

But what can you do when you have common functionality applied to multiple functions? You refactor to use decorators of course! So, may I present, the meta-decorator. (I also upped the ante by changing b to return a wrapper)

def deco_tracker(decorator):
    """ Meta-decorator for attaching decorator reference to decorated function
    Applying this decorator to another decorator will result in the final decorated function
    having an attribute called 'decorators' that stores references to it's decorators.
    """
    def deco_wrapper(func):
        returned_func = decorator(func)  # Sometimes a wrapper is returned
        try:
            returned_func.decorators.append(decorator)
        except AttributeError:
            returned_func.decorators = getattr(func, 'decorators', []) + [decorator]
        return returned_func
    return deco_wrapper

@deco_tracker
def a(func):
    # decorator business
    return func

def b(arg):
    @deco_tracker
    def _deco(func)
        def wrapper(*args, **kwargs)
            # decorator business
            return func(*args, **kwargs)
        return wrapper
    return _deco

@a
@b(arg=1)
def my_func():
    pass

print(my_func.decorators)  # [<function b.<locals>._deco 0x19>, <function a at 0x7f>]

It was easier to store the function reference instead of a string representation of the name. It negates the complexity of parsing a wrapper.

Meta-decorator can be applied to third party decorators as long as they don't return a wrapper. For that you would need to wrap deco_tracker to produce deco_wrapper_tracker then apply the correct meta-decorator variant to each decorator. An enhancement to that would be to check if the first incoming argument isinstance of types.FunctionType and select from there but that assumes decorator that takes args never take a function, pretty safe bet but not ideal. If this gets enough attention I'll produce the example code.

Guy Gangemi
  • 1,533
  • 1
  • 13
  • 25