Jayendra Parmar
Jayendra Parmar

Reputation: 774

Python decorator to keep signature and user defined attribute

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?

Upvotes: 9

Views: 2877

Answers (4)

smarie
smarie

Reputation: 5156

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

Upvotes: 0

plaes
plaes

Reputation: 32716

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

Upvotes: 12

Olivier Melan&#231;on
Olivier Melan&#231;on

Reputation: 22314

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.

Upvotes: 3

Tarun Lalwani
Tarun Lalwani

Reputation: 146510

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'

Upvotes: 6

Related Questions