The following wrapper makes the cached function look more like the original.
from functools import wraps
def restore(func):
@wraps(func.__wrapped__)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
It creates yet another wrapper around your decorated function that restores its type as a function while preserving the docstring.
For example, if you have a function like this:
@restore
@lru_cache
def func_dup(x: int):
"""my doc"""
return x
Then, run help(func_dup)
Help on function func_dup in module __main__:
func_dup(x: int)
my doc
Why the Difference
I will be using CPython 3.10, which is the latest version as of the time I wrote this answer.
The help
callable is actually implemented in pydoc
as a Helper
object. The magic method Helper.__call__
is defined to call Helper.help
. It then calls doc
, which calls render_doc
. The render_doc
function makes up the string that gets printed. Inside this function, it calls pydoc.describe
for a descriptive name for your function.
Your original mymodule.myfunction
is a function, so describe
returns in this branch.
if inspect.isfunction(thing):
return 'function ' + thing.__name__
This gives "function myfunction"
.
However, after you decorate your function with @lru_cache
, it becomes an instance of the built-in/extension type functools._lru_cache_wrapper
. I am not sure why it is implemented this way, but the decorated function is not of type types.FunctionType
anymore. So the describe(mymodule.myfunction)
function returns on the last line after being decorated.
return type(thing).__name__
This returns "_lru_cache_wrapper"
.
The functools.update_wrapper
function attempts to
Update a wrapper function to look like the wrapped function
It doesn't restore the wrapper as an instance of types.FunctionType
. It does, however, reference the original function in the __wrapped__
attribute. Hence, we can use that to wrap your original function yet again.
Reference
There is a Python Issue bpo-46761 that may or may not relate to this issue.
when using functools.partial() to pre-supply arguments to a function, if you then call functools.update_wrapper() to update that partial object, inspect.signature() returns the original function's signature, not the wrapped function's signature.
It is primarily on functools.partial
, which doesn't even preserve the wrapped function's signature.