如何通过反射获取方法上的装饰器名称?

43 投票
9 回答
31989 浏览
提问于 2025-04-16 01:11

我正在尝试找出一个方法上所有装饰器的名字。我已经可以获取到方法的名字和文档字符串,但就是不知道怎么才能得到装饰器的列表。

9 个回答

13

我也问过同样的问题。在我的单元测试中,我只是想确保给定的函数或方法使用了装饰器。

装饰器已经单独测试过,所以我不需要测试每个被装饰函数的共同逻辑,只需要确认装饰器被使用了。

最后,我想出了以下这个辅助函数:

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] == "@"
    ]

这个列表推导式有点“复杂”,但它能解决问题,在我的情况下,它是一个测试辅助函数。

如果你只关心装饰器的名称,而不关心可能的装饰器参数,它就能正常工作。如果你想支持带参数的装饰器,可以用类似 line.strip().split()[0].split("(")[0] 的方式来实现(这个还没测试过)。

最后,如果你想去掉“@”符号,可以把 line.strip().split()[0] 替换成 line.strip().split()[0][1:]

52

我很惊讶这个问题这么久了居然没有人花时间来分享真正的自省方法,所以我来补充一下:

你想要检查的代码是……

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

现在你可以用下面的方式来检查上面的 Foo 类……

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)

这样应该会打印出类似下面的内容……

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

或者至少在我用 Python 2.7.9 快速测试的时候是这样的 :)

35

如果你能改变调用装饰器的方式,从

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

变成

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

那么你可以这样注册装饰器:

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

举个例子:

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

在这里我们访问装饰器的元组:

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

在这里我们只打印装饰器的名字:

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

撰写回答