61

here is my decorator:

def check_domain(func):

    def wrapper(domain_id, *args, **kwargs):
        domain = get_object_or_None(Domain, id=domain_id)
        if not domain:
            return None
        return func(domain_id, *args, **kwargs)

    return wrapper

Here is a wrapped up function:

@check_domain
def collect_data(domain_id, from_date, to_date):
    do_stuff(...)

If I do collect_data.__name__ I get wrapper instead of collect_data

Any ideas?

SilentGhost
  • 307,395
  • 66
  • 306
  • 293
RadiantHex
  • 24,907
  • 47
  • 148
  • 244

6 Answers6

99

functools.wraps is not needed! Just use func.__name__

import time

def timeit(func):
    def timed(*args, **kwargs):
        ts = time.time()
        result = func(*args, **kwargs)
        te = time.time()
        print('Function', func.__name__, 'time:', round((te -ts)*1000,1), 'ms')
        print()
        return result
    return timed

@timeit
def math_harder():
    [x**(x%17)^x%17 for x in range(1,5555)]
math_harder()

@timeit
def sleeper_agent():
    time.sleep(1)
sleeper_agent()

Outputs:

Function math_harder time: 8.4 ms
Function sleeper_agent time: 1003.7 ms
valex
  • 5,163
  • 2
  • 33
  • 40
Zach Estela
  • 1,282
  • 1
  • 8
  • 10
  • 4
    In the OP's case, `functools.wraps` is not needed, you're right - though, using it still is the better approach in most (other) cases. – s-m-e Dec 06 '17 at 14:57
  • yeah this version doesnt work in some cases, functools wraps is the right choice – Manuel G Oct 22 '19 at 13:09
  • I found this nice [explained example](https://realpython.com/primer-on-python-decorators/) – Sylvain Jan 17 '20 at 11:24
  • @ManuelG, can you explain how wraps works in some cases that this solution doesn't? From what I can tell, Zach's solution allows me to have one self contained decorator to do exactly what I need (even for the exact use case - i.e., profiling code) so not trying to get biased towards this answer but if you have any counter examples - I'm very much interested. Thanks! – LeanMan Oct 20 '20 at 17:00
  • While this answer suggests how it could be possible to solve the problem from the question it does not really solve it. In your code the decorated function name is not fixed: `sleeper_agent.__name__ == 'timed'`. Besides that `functools.wraps` does not only correct `__name__` it also corrects for example `__doc__`. Certainly do use `functools.wraps` and do not reinvent the wheel. See https://stackoverflow.com/questions/308999/what-does-functools-wraps-do – pabouk - Ukraine stay strong Jun 21 '21 at 16:56
  • The timing function like this answer [here](https://realpython.com/primer-on-python-decorators/#timing-functions) use `@functools.wraps`. – Muhammad Yasirroni Feb 12 '23 at 01:12
51

You may want to use wraps from functools. See the example

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwargs):
...         print('Calling decorated function')
...         return f(*args, **kwargs)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'
valex
  • 5,163
  • 2
  • 33
  • 40
Tommaso Barbugli
  • 11,781
  • 2
  • 42
  • 41
7

In addition to functools.wraps, you can check out the decorator module which was designed to help with this problem.

tkerwin
  • 9,559
  • 1
  • 31
  • 47
3

Check out functools.wraps. Requires python 2.5 or higher though, if that's an issue.

meshantz
  • 1,566
  • 10
  • 17
2

For anyone who needs to access the decorated function name when there are multiple decorators, like this:

@decorator1
@decorator2
@decorator3
def decorated_func(stuff):
    return stuff

the functools.wraps mentioned above solves that.

Zevgon
  • 556
  • 4
  • 13