pythonpython-typingpython-decorators

How to correctly add type hints to a decorator?


I'm currently writing and learning about decorators (with and without arguments). Here is an example of a decorator with one argument that redirects the output of a function to a log file. The code works, but my questions are the following:

  1. What is the correct way to use type hints when dealing with decorators?
  2. Are decorators supposed to have docstrings? If so, in my example, is the docstring correct/complete?

Thank you in advance!

def redirect_output(log_file):
    """
    Decorator to redirect the stdout written output of a function to a specified
        log file.
    Args:
        log_file (str): The path to the log file where the output will be redirected.
    """

    def redirect_output_decorator(func):

        @functools.wraps(func)
        def redirect_output_wrapper(*args, **kwargs) -> Any:
            output = StringIO()

            with redirect_stdout(output):
                func(*args, **kwargs)

            output_lines = output.getvalue().splitlines()

            if output_lines:
                file_logger("Output from {}: ".format(func.__name__), log_file)

                for line in output_lines:
                    file_logger(line, log_file, with_date=False)

        return redirect_output_wrapper

    return redirect_output_decorator

Solution

  • ParamSpec is a solution to this problem. Here is the code example in its document.

    from typing import TypeVar, ParamSpec
    import logging
    
    T = TypeVar('T')
    P = ParamSpec('P')
    
    def add_logging(f: Callable[P, T]) -> Callable[P, T]:
        '''A type-safe decorator to add logging to a function.'''
        def inner(*args: P.args, **kwargs: P.kwargs) -> T:
            logging.info(f'{f.__name__} was called')
            return f(*args, **kwargs)
        return inner
    

    It is officially supported after Python 3.10, but is also supported by importing from typing_extensions(https://github.com/python/typing_extensions) for python version greater than 3.7