9

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?

Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
Jayendra Parmar
  • 702
  • 12
  • 30
  • Are you using this [decorator](https://pypi.python.org/pypi/decorator) module? – PM 2Ring Feb 12 '18 at 12:50
  • Possible duplicate of [Preserving signatures of decorated functions](https://stackoverflow.com/questions/147816/preserving-signatures-of-decorated-functions) – plaes Feb 12 '18 at 13:04
  • It does not seem to be the correct way to use decorator.decorator if we are talking about the same module. – Olivier Melançon Feb 16 '18 at 21:59

4 Answers4

12

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_)
plaes
  • 31,788
  • 11
  • 91
  • 89
  • @bruce_wayne Works in python3.6 – plaes Feb 12 '18 at 13:01
  • as of python 3.7 or less, `functools.wraps` does not actually preserve the signature. It appears to preserve it, but this is because `inspect.signature` follows the `__wrapped__` magic attribute that it sets. There are two drawbacks with it, the major one being that users do not get the `TypeError` when they use the wrapper with invalid arguments, it is still executed until it calls the wrapped `func` (that will raise the error eventually). See https://stackoverflow.com/questions/308999/what-does-functools-wraps-do/55102697#55102697 – smarie Mar 14 '19 at 12:52
6

Working

@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'
Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
  • This is exactly right for Python 2, thanks! The decorator module also breaks func_globals, but I was able to preserve those by copying them over from the original `func` as well. – Dana Cartwright Feb 17 '18 at 20:39
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.

Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
0

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
smarie
  • 4,568
  • 24
  • 39