Python装饰器保持签名和用户定义的属性

时间:2018-02-12 12:31:19

标签: python python-2.7 ipython python-decorators

我有一个简单的装饰器my_decorator来装饰my_func

def my_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    wrapper._decorator_name_ = 'my_decorator'
    return wrapper

@my_decorator
def my_func(x):
    print('hello %s'%x)

my_func._decorator_name_
'my_decorator'

直到这里工作,但我无法看到该功能的实际签名。

my_func?
Signature: my_func(*args, **kwargs)
Docstring: <no docstring>
File:      ~/<ipython-input-2-e4c91999ef66>
Type:      function

如果我用python&#39; s decorator.decorator装饰我的装饰者,我可以看到我的功能的签名,但我不能拥有我定义的新属性。

import decorator

@decorator.decorator
def my_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    wrapper._decorator_name_ = 'my_decorator'
    return wrapper

@my_decorator
def my_func(x):
    print('hello %s'%x)

my_func?
Signature: my_func(x)
Docstring: <no docstring>
File:      ~/<ipython-input-8-934f46134434>
Type:      function

my_func._decorator_name_
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-10-7e3ef4ebfc8b> in <module>()
----> 1 my_func._decorator_name_

AttributeError: 'function' object has no attribute '_decorator_name_'

我怎样才能在python2.7中同时拥有它们?

4 个答案:

答案 0 :(得分:11)

对于Python 3,在标准库中使用functools.wraps

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    wrapper._decorator_name_ = 'my_decorator'
    return wrapper

@my_decorator
def my_func(x):
    print('hello %s'%x)

print(my_func._decorator_name_)

答案 1 :(得分:5)

Working

@decorator.decorator返回一个函数,该函数将另一个函数作为输入。在您的情况下,您需要返回函数的属性。

要使它在Python 2.7上运行,你只需要一些调整

import decorator

def my_dec2(func):
    @decorator.decorator
    def my_decorator(func, *args, **kwargs):
        print("this was called")
        return func(*args, **kwargs)

    test = my_decorator(func)
    test._decorator_name_ = "my_decorator"
    return test

@my_dec2
def my_func(x):
    print('hello %s'%x)


my_func(2)
print(my_func._decorator_name_)

然后当你测试时它起作用

In [1]: my_func?
Signature: my_func(x)
Docstring: <no docstring>
File:      ~/Desktop/payu/projects/decotest/decos.py
Type:      function

In [2]: my_func._decorator_name_
Out[2]: 'my_decorator'

答案 2 :(得分:4)

如果您想以某种方式改变函数的行为,则只需要定义wrapper函数。因此,如果您真的只想在不改变其行为的情况下为函数添加某些属性,则可以执行以下操作。

def my_decorator(func):
    func._decorator_name_ = 'my_decorator'
    return func

在你需要wrapper的更复杂的情况下,我建议遵循accepted answer for this question,它解释了如何创建一个行为与另一个相同但具有自定义签名的函数。使用inspect.getargspec,您可以从my_func恢复签名并将其转置为wrapper

答案 3 :(得分:0)

正如其他人指出的那样,您似乎没有正确使用decorator

或者,您可以使用我的库makefun创建保留签名的包装程序,它与decorator依赖于相同的技巧来保存签名,但是更着重于动态函数的创建并且更加通用(您可以更改签名):

from makefun import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    wrapper._decorator_name_ = 'my_decorator'
    return wrapper

您可以检查它是否按预期工作:

@my_decorator
def my_func(x):
    """my function"""
    print('hello %s' % x)

assert my_func._decorator_name_ == 'my_decorator'
help(my_func)

关于它的价值,如果您希望以后在装饰器中添加可选参数而不使代码看起来更复杂,请查看decopatch。例如,如果您希望_decorator_name_是装饰器的可选参数:

from decopatch import function_decorator, DECORATED
from makefun import wraps

@function_decorator
def my_decorator(name='my_decorator', func=DECORATED):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    wrapper._decorator_name_ = name
    return wrapper