如何使用装饰器提供函数元数据?

时间:2014-03-10 16:44:59

标签: python decorator

基于this thread,我有以下

import functools
def predicate(author, version, **others):
    def _predicate(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            func.meta = {
                'author':  author,
                'version': version
            }
            func.meta.update(others)
            func(*args, **kwargs)
        return wrapper
    return _predicate

但是我无法得到

的简单用例
@predicate('some author', 'some version')
def second_of_two(a, b):
    return b

按预期工作:

>>> second_of_two.meta['author']
'some author'
>>> second_of_two(1, 2)
2

我在这里做错了什么?

2 个答案:

答案 0 :(得分:3)

好吧,我不明白为什么你在这里使用functools的东西,而你需要把它变成一个带参数的简单装饰器:

def predicate(author, version, **others):
    def _predicate(func):
        func.meta = {
            'author':  author,
            'version': version
        }
        func.meta.update(others)
        return func
    return _predicate

@predicate('foo', 'bar')
def myfunc(i,j):
    return i+j

print myfunc.meta
print myfunc(1,2)

给出:

{'version': 'bar', 'author': 'foo'}
3

答案 1 :(得分:2)

你做错了很多事,所以让我们看看它:

在您的代码中,predicate是装饰器的生成器;实际的装饰者是_predicate。装饰器接受一个函数并返回一个函数;后者将被分配到添加原始代码的位置。因此,使用@predicate(…) def second_of_two,名称second_of_two将获得装饰器返回的值。

让我们看看你的装饰师做了什么:

def _predicate(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        …
    return wrapper

它构建一个新的内部函数,使用functool.wraps(这是一个好习惯),然后返回该函数。到现在为止还挺好;所以second_of_two正确地获得了装饰函数。

现在,包装器函数做了什么?

def wrapper(*args, **kwargs):
    func.meta = {
        'author':  author,
        'version': version
    }
    func.meta.update(others)
    func(*args, **kwargs)

包装函数在原始函数对象上设置元数据。这是第一个错误;您在原始函数上设置元数据,以后不再参考。 second_of_two是包装函数,因此它没有那些元函数。此外,您只能在函数运行时设置元数据 。因此,如果您在包装器函数上正确设置元数据,那么只有在您调用一次后它仍然可用。第三个错误是你调用原始函数并简单地抛弃它的返回值。这就是为什么你没有得到任何输出。

那你该怎么做呢?首先,将元数据设置在包装函数之外,并将其设置在您返回的函数上。在包装函数内部,返回原始函数的返回值:

def predicate(author, version, **others):
    def _predicate(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        wrapper.meta = {
            'author':  author,
            'version': version
        }
        wrapper.meta.update(others)

        return wrapper
    return _predicate

现在,您的wrapper函数不再有任何特殊功能,因此您只需使用原始函数:

def predicate(author, version, **others):
    def _predicate(func):
        func.meta = {
            'author':  author,
            'version': version
        }
        func.meta.update(others)
        return func
    return _predicate