Python decorator to keep signature and user defined attribute

◇◆丶佛笑我妖孽 提交于 2019-12-21 10:18:26

问题


I have my simple decorator my_decorator which decorates the 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'

Till here things work, but I can't see the actual signature of the function.

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

If I decorate my decorator with python's decorator.decorator, I can see the signature of my function but I can't have the new property which I have defined.

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

How can I have both in python2.7?


回答1:


For Python 3, using functools.wraps in standard library:

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



回答2:


@decorator.decorator returns a function which takes another function as input. In your case you want the attribute on the returned function.

For it to work on Python 2.7, you just need some tweak

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

And then when you test it works

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'



回答3:


You only need to define a wrapper function if you somehow want to alterate the behaviour of your function. So in the case you really just want to add some attribute to your function without changing its behaviour, you can simply do the following.

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

In the more complex case where you need to have a wrapper, what I suggest is following the accepted answer for this question which explains how to create a function that behaves the same as another, but has a customised signature. Using inspect.getargspec you can recover the signature from my_func and transpose it to your wrapper.




回答4:


As others have pointed out, you do not seem to use decorator correctly.

Alternately you can use my library makefun to create your signature-preserving wrapper, it relies on the same trick than decorator to preserve signature, but is more focused on dynamic function creation and is more generic (you can change the signatures):

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

You can check that it works as expected:

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

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

For what it's worth, if you wish to later add optional arguments to your decorator without making the code look more complex, have a look at decopatch. For example if you want _decorator_name_ to be an optional argument of the decorator:

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


来源:https://stackoverflow.com/questions/48746567/python-decorator-to-keep-signature-and-user-defined-attribute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!