自省以获取方法上的装饰器名称?

问题描述 投票:0回答:9

我想弄清楚如何获取方法上所有装饰器的名称。我已经可以获取方法名称和文档字符串,但无法弄清楚如何获取装饰器列表。

python decorator introspection
9个回答
51
投票

我很惊讶这个问题这么老了,没有人花时间添加实际的内省方法来做到这一点,所以这里是:

您要检查的代码...

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

10
投票

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

装饰器是单独测试的,因此我不需要测试每个装饰函数的通用逻辑,只需使用装饰器即可。

我终于想出了以下辅助函数:

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

来删除“@”

2
投票

正如 Faisal 指出的那样,您可以让装饰器本身将元数据附加到函数,但据我所知,这不会自动完成。


1
投票

那是因为装饰器是“语法糖”。假设您有以下装饰器:

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

然后将其应用到函数中:

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

这相当于:

thisFunction = MyDecorator(thisFunction)

如果您可以控制装饰器,您也许可以将“历史记录”嵌入到函数对象中。我敢打赌还有其他一些聪明的方法可以做到这一点(也许通过覆盖赋值),但不幸的是我不太精通 Python。 :(


1
投票

ast
可用于解析源代码。

import ast


with open('./tmp1.py') as f:
    code_text = f.read()


parsed = ast.parse(code_text)

这是我尝试解析装饰器的解析对象。此实现递归地搜索应用的装饰器。它将捕获函数、类和方法以及嵌套时

@
的使用情况。

import ast


def flatten(decorator_list):
    for d in decorator_list:
        try:
            yield d.id
        except AttributeError:
            yield d.func.id


def parse_decorables(body, indent=0):
    for node in body:
        if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
            print('    '*indent, node.name, '-', *flatten(node.decorator_list))
            parse_decorables(node.body, indent+1)


with open('./tmp1.py') as f:
    code_text = f.read()


parsed = ast.parse(code_text)
parse_decorables(parsed.body)

考虑这个问题时需要记住一些事情。装饰器可能只是向函数添加一个属性并返回它。此时,装饰器和原始函数之间没有任何关联。因此,无法动态检查函数并确保我们看到应用于它的所有装饰器。

所以我们转到这个

ast
解决方案并检查代码。然而,它有其自身的局限性。导入的装饰器可能会向该函数应用更多的装饰器,这不会捕捉到这一点。如果您按照引入
@
之前的方式“应用装饰器”(f = deco(f)),则不会捕获到这一点。所以,实际上我们只是在源文件中捕获
@
的使用,这可能适合您的应用程序。


0
投票

我认为这是不可能的。装饰器不是方法的某种属性或元数据。装饰器是一种用函数调用的结果替换函数的便捷语法。有关更多详细信息,请参阅 http://docs.python.org/whatsnew/2.4.html?highlight=decorators#pep-318-decorators-for-functions-and-methods


0
投票
用一般的方式是不可能做到的,因为

@foo def bar ...

完全相同

def bar ... bar = foo (bar)

您可以在某些特殊情况下做到这一点,例如通过分析函数对象可能

@staticmethod

,但不会比这更好。


0
投票
你不能,但更糟糕的是,存在一些库可以帮助隐藏你已经装饰了一个函数的事实。有关更多信息,请参阅

Functools 或装饰器库(@decorator

,如果我能找到它)。

© www.soinside.com 2019 - 2024. All rights reserved.