Introspection to get decorator names on a method?

Question:

I am trying to figure out how to get the names of all decorators on a method. I can already get the method name and docstring, but cannot figure out how to get a list of decorators.

Asked By: Tony

||

Answers:

That’s not possible in my opinion. A decorator is not some kind of attribute or meta data of a method. A decorator is a convenient syntax for replacing a function with the result of a function call. See http://docs.python.org/whatsnew/2.4.html?highlight=decorators#pep-318-decorators-for-functions-and-methods for more details.

Answered By: Achim

It is impossible to do in a general way, because

@foo
def bar ...

is exactly the same as

def bar ...
bar = foo (bar)

You may do it in certain special cases, like probably @staticmethod by analyzing function objects, but not better than that.

Answered By: user319799

That’s because decorators are “syntactic sugar”. Say you have the following decorator:

def MyDecorator(func):
    def transformed(*args):
        print "Calling func " + func.__name__
        func()
    return transformed

And you apply it to a function:

@MyDecorator
def thisFunction():
    print "Hello!"

This is equivalent to:

thisFunction = MyDecorator(thisFunction)

You could embed a “history” into the function object, perhaps, if you’re in control of the decorators. I bet there’s some other clever way to do this (perhaps by overriding assignment), but I’m not that well-versed in Python unfortunately. 🙁

Answered By: Faisal

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

Answered By: Russell Borogove

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']
Answered By: unutbu

You can’t but even worse is there exists libraries to help hide the fact that you have decorated a function to begin with. See Functools or the decorator library (@decorator if I could find it) for more information.

Answered By: wheaties

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 🙂

Answered By: Jaymon

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:]

Answered By: dbu
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.