12

The functionality of the decorator module and functools.wraps is closely related. What are the differences between the two (as of Python 3.3 / 3.4)?

I am aware of one difference: 3+ years ago, decorator supported help, while wraps didn't (see also this).

Community
  • 1
  • 1
max
  • 49,282
  • 56
  • 208
  • 355
  • another example: [Preserving signatures of decorated functions](http://stackoverflow.com/q/147816/4279) – jfs Dec 14 '15 at 13:57

3 Answers3

6

One of the main differences is listed right in the documentation you linked to: decorator preserves the signature of the wrapped function, while wraps does not.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • 1
    This seems to be no longer the case, is that correct? – chtenb Dec 12 '15 at 16:02
  • @ChieltenBrinke: You mean that `functools.wraps` now preserves signature? I don't see anything about that in the docs, but I don't have Python 3.5 installed to test it. – BrenBarn Dec 12 '15 at 21:58
  • Agreed its not in the docs, but testing it in the python shell (3.4) seemed to say it does work – chtenb Dec 13 '15 at 00:30
  • @ChieltenBrinke: Doesn't work for me. What did you test? Did you read the `decorator` docs linked in the question? By "preserve signature" we mean that `inspect.getargspec` returns the "real" arguments of arguments of the original functions, not a generic `*args`/`**kwargs` signature. – BrenBarn Dec 13 '15 at 02:58
  • @ChieltenBrinke: Aha, you are right. `getargspec` is now deprecated and `signature` works properly. – BrenBarn Dec 14 '15 at 09:05
  • As per python 3.7: `functools.wraps` preserves signature but only in appearance because `help` and `signature` follow the `__wrapped__` attribute ; it does not raise correct errors in case of misuse. See answer below: https://stackoverflow.com/a/55363013/7262247 – smarie Mar 26 '19 at 17:29
6

Per the discussion with BrenBarn, nowadays functools.wraps also preserves the signature of the wrapped function. IMHO this makes the decorator decorator almost obsolete.

from inspect import signature
from functools import wraps

def dec(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

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

def foo(a: int, b):
    pass

print(signature(dec(foo)))
print(signature(dec2(foo)))

# Prints:
# (*args, **kwargs)
# (a:int, b)

Note that one has to use signature and not getargspec. Tested with python 3.4.

chtenb
  • 14,924
  • 14
  • 78
  • 116
  • `getargspec` or newer and recommended `getfullargspec` don't work on their own, but when used in combination with `inspect.unwrap` they do: https://docs.python.org/3/library/inspect.html#inspect.unwrap – Pekka Klärck Nov 14 '19 at 13:19
5

There are two differences:

  • decorator truly preserves signature while functools.wraps does not, even in python 3.7. By signature, I mean help() and signature() and all the __dict__ of course, but also that the wrapper raises correct TypeError without executing at all in case wrong arguments are provided by users. As explained in this post, functools.wraps appears to preserve signature but does not really preserve it.

  • with decorator you always receive the arguments in kwargs when they are not var-positional - this makes it much easier to implement your wrapper. With functools.wraps it is much more difficult to get an argument value based on its name, as it might be in *args, in **kwargs, or nowhere (if it is an optional argument and was not provided by the user)

Since I liked functool.wraps API very much but wanted to solve the above two problems, I created makefun. It proposes a generalization of @wraps that uses the exact same trick than decorator, and even supports signature modifications such as adding and removing parameters. It is already used by several projects, do not hesitate to give it a try!

smarie
  • 4,568
  • 24
  • 39