Introspection to get decorator names on a method?

后端 未结 8 1569
滥情空心
滥情空心 2020-12-01 08:00

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 dec

相关标签:
8条回答
  • 2020-12-01 08:00

    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. :(

    0 讨论(0)
  • 2020-12-01 08:05

    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.

    0 讨论(0)
  • 2020-12-01 08:07

    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.

    0 讨论(0)
  • 2020-12-01 08:11

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

    0 讨论(0)
  • 2020-12-01 08:17

    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.

    0 讨论(0)
  • 2020-12-01 08:18

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

    0 讨论(0)
提交回复
热议问题