Introspection to get decorator names on a method?

I've add the same question. In my unit tests I just wanted to make sure decorators were used by given functions/methods.

The decorators were tested separately so I didn't need to test the common logic for each decorated function, just that the decorators were used.

I finally came up with the following helper function:

import inspect

def get_decorators(function):
    """Returns list of decorators names

    Args:
        function (Callable): decorated method/function

    Return:
        List of decorators as strings

    Example:
        Given:

        @my_decorator
        @another_decorator
        def decorated_function():
            pass

        >>> get_decorators(decorated_function)
        ['@my_decorator', '@another_decorator']

    """
    source = inspect.getsource(function)
    index = source.find("def ")
    return [
        line.strip().split()[0]
        for line in source[:index].strip().splitlines()
        if line.strip()[0] == "@"
    ]

With the list comprehension, it is a bit "dense" but it does the trick and in my case it's a test helper function.

It works if you are intrested only in the decorators names, not potential decorator arguments. If you want to support decorators taking arguments, something like line.strip().split()[0].split("(")[0] could do the trick (untested)

Finally, you can remove the "@" if you'd like by replacing line.strip().split()[0] by line.strip().split()[0][1:]


As Faisal notes, you could have the decorators themselves attach metadata to the function, but to my knowledge it isn't automatically done.


I'm surprised that this question is so old and no one has taken the time to add the actual introspective way to do this, so here it is:

The code you want to inspect...

def template(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

baz = template
che = template

class Foo(object):
    @baz
    @che
    def bar(self):
        pass

Now you can inspect the above Foo class with something like this...

import ast
import inspect

def get_decorators(cls):
    target = cls
    decorators = {}

    def visit_FunctionDef(node):
        decorators[node.name] = []
        for n in node.decorator_list:
            name = ''
            if isinstance(n, ast.Call):
                name = n.func.attr if isinstance(n.func, ast.Attribute) else n.func.id
            else:
                name = n.attr if isinstance(n, ast.Attribute) else n.id

            decorators[node.name].append(name)

    node_iter = ast.NodeVisitor()
    node_iter.visit_FunctionDef = visit_FunctionDef
    node_iter.visit(ast.parse(inspect.getsource(target)))
    return decorators

print get_decorators(Foo)

That should print something like this...

{'bar': ['baz', 'che']}

or at least it did when I tested this with Python 2.7.9 real quick :)


If you can change the way you call the decorators from

class Foo(object):
    @many
    @decorators
    @here
    def bar(self):
        pass

to

class Foo(object):
    @register(many,decos,here)
    def bar(self):
        pass

then you could register the decorators this way:

def register(*decorators):
    def register_wrapper(func):
        for deco in decorators[::-1]:
            func=deco(func)
        func._decorators=decorators        
        return func
    return register_wrapper

For example:

def many(f):
    def wrapper(*args,**kwds):
        return f(*args,**kwds)
    return wrapper

decos = here = many

class Foo(object):
    @register(many,decos,here)
    def bar(self):
        pass

foo=Foo()

Here we access the tuple of decorators:

print(foo.bar._decorators)
# (<function many at 0xb76d9d14>, <function decos at 0xb76d9d4c>, <function here at 0xb76d9d84>)

Here we print just the names of the decorators:

print([d.func_name for d in foo.bar._decorators])
# ['many', 'decos', 'here']