Copying the docstring of function onto another function by name

Question:

I’m looking to copy the docstring of a function in the same file by name (with a decorator).

I can easily do it with a function that is out of the current module, but I’m a bit confused when it comes to the same module (or the same class more specifically)

Here’s what I have so far:

import inspect

def copy_doc(func_name: str):
    def wrapper(func):
        doc = ... # get doc from function that has the name as func_name
        func.__doc__ = doc
        return func
    retun wrapper

I’m looking for something that can do two of these examples:

Ex 1:

def this() -> None:
    """Fun doc string"""
    return

@copy_doc('this')
def that() -> None:
    return

print(that.__doc__)

Ex 2:

class This:
    def foo(self) -> None:
        """Fun doc string"""
        return None

    @copy_doc('foo')
    def bar(self) -> None:
        return None

print(This().bar.__doc__)

Any fun ideas?

Asked By: Iced Chai

||

Answers:

After some testing, I learned you could do something like this:

from typing import Callable

def copy_doc(copy_func: Callable) -> Callable:
    """Use Example: copy_doc(self.copy_func)(self.func) or used as deco"""
    def wrapper(func: Callable) -> Callable:
        func.__doc__ = copy_func.__doc__
        return func
    return wrapper

class Test:
    def foo(self) -> None:
        """Woa"""
        ...
    
    @copy_doc(foo)
    def this(self) -> None:
        ...
    

print(Test().this.__doc__)

# Outputs:
> Woa
Answered By: Iced Chai

Here is an expansion of Iced Chai’s answer that will also copy over the function signature as well:

from typing import Callable, TypeVar, ParamSpec

P = ParamSpec("P")
T = TypeVar("T")

def wraps(wrapper: Callable[P, T]):
    """An implementation of functools.wraps."""

    def decorator(func: Callable) -> Callable[P, T]:
        func.__doc__ = wrapper.__doc__
        return func

    return decorator

Please note that on Python version <= 3.9 ParamSpec is imported from typing_extensions instead from typing. If you need cross version compatibility you can import like this:

import sys
from typing import Callable, TypeVar
if sys.version_info <= (3, 9):
    from typing_extensions import ParamSpec
else:
    from typing import ParamSpec

Example usage

def func(x: int, y: int) -> str:
    """I have a docstring and arguments"""

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

Your IDE intellisense/autocomplete should suggest/preview the signature and docstring of func when typing anotherfunc(). I tested this on VSCode and it worked for me. I often use this approach instead of importing functools.wraps because functools.wraps doesn’t seem to copying the function signature for VSCode’s intellisense.

On VSCode when I type anotherfunc() the intellisense suggestion popup shows:

(x: int, y: int) -> str
-------------------------
I have a docstring

Here is why it works for anyone curious:

Callable[P, T] essentially means "A function that takes in P parameters and returns a type T". ParamSpec reserves not just the argument types, but also their names, order, defaults, and whether they are positional arguments or keyword arguments. Typing two variables with `Callable[P, T] within some common scope is the same as saying "Both of these functions take the same arguments and return the same type"

Here we type hint both the wrapper parameter of wraps and the return type of decorator with Callable[P, T]. This tells editor’s static type checker that the resulting function from decoration has the same signature as the input parameter of the wraps function.

More concretely, using the example above, we’re saying that the signature of the resulting function from decorating anotherfunc has the same signature as func.

Resources:

Answered By: Kevin Jerome
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.