5

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?

Iced Chai
  • 472
  • 2
  • 13

2 Answers2

10

After some testing and experimentation, I learned you could directly reference the function in a given class.

* Note ParamSpec and TypeVar are to keep the correct signature of the wrapped function, you can remove all the annotations if you do not need them.

from typing import Callable, TypeVar, Any, TypeAlias
from typing_extensions import ParamSpec

T = TypeVar('T')
P = ParamSpec('P')
WrappedFuncDeco: TypeAlias = Callable[[Callable[P, T]], Callable[P, T]]


def copy_doc(copy_func: Callable[..., Any]) -> WrappedFuncDeco[P, T]:
    """Copies the doc string of the given function to another. 
    This function is intended to be used as a decorator.

    .. code-block:: python3

        def foo():
            '''This is a foo doc string'''
            ...

        @copy_doc(foo)
        def bar():
            ...
    """

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

    return wrapped


class Cas:
    def foo(self) -> None:
        """This is the foo doc string."""
        return

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


print(Cas.bar.__doc__)
# >>> This is the foo doc string.
Iced Chai
  • 472
  • 2
  • 13
1

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: