{
"$type": "site.standard.document",
"canonicalUrl": "https://rednafi.com/python/internals-of-functools-wraps/",
"description": "Explore how functools.wraps preserves function identity by copying metadata from wrapped functions using update_wrapper and partial application.",
"path": "/python/internals-of-functools-wraps/",
"publishedAt": "2022-02-14T00:00:00.000Z",
"site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
"tags": [
"Python"
],
"textContent": "The functools.wraps decorator allows you to keep your function's identity intact after\nit's been wrapped by a decorator. Whenever a function is wrapped by a decorator, identity\nproperties like - function name, docstring, annotations of it get replaced by those of the\nwrapper function. Consider this example:\n\nHere, I've defined a simple logging decorator that wraps the add function. The function\nadd has its own type annotations and docstring. So, you'd expect the docstring and\nname* of the add function to be printed when the above snippet gets executed. However,\nrunning the script prints the following instead:\n\nThis is surprising and probably not something you want. If you pay attention to the function\nwrapper in the log decorator, you'll see that the identity properties of the wrapper\nfunction replace the identity properties of the wrapped function add. This can easily be\navoided by decorating the wrapper function inside the log decorator with the\nfunctools.wraps decorator:\n\nNow, running the script will return the expected output:\n\nI wanted to take a peek into how the functools.wraps decorator works internally. Turns out\nthat the implementation is quite straightforward. Here's the entire implementation from the\nfunctools.py module. For brevity's sake, I've stripped out the comments and added type\nannotations:\n\nThe bulk of the work is done in the update_wrapper function. It copies the identity\nproperties defined in WRAPPER_ASSIGNMENTS and WRAPPER_UPDATES - from the wrapped\nfunction over to the wrapper function. Here, the wrapped function is the decorated one\n(add function) and the wrapper function is the eponymous function inside the log\ndecorator.\n\nSince you've already seen that whenever you try to introspect the identity properties of a\nwrapped function, the wrapper function obfuscates them and returns its own properties.\nHowever, if the identity properties are copied over from the wrapped to the wrapper\nfunction, your inspection will return the expected result. The update_wrapper function is\ndoing exactly that.\n\nThe wraps function just binds the input arguments with the update_wrapper function using\nthe partial function defined in the same module. This allows us to use the wraps\nfunction as a decorator.\n\nYou can also directly use the update_wrapper function to get the same result should you\nchoose to do so. Here's how to do it:\n\nFurther reading\n\n- [functools.update_wrapper source code]\n\n\n\n\n[functools.update_wrapper source code]:\n https://github.com/python/cpython/blob/0ae40191793da1877a12d512f0116d99301b2c51/Lib/functools.py#L35",
"title": "Peeking into the internals of Python's 'functools.wraps' decorator"
}