Is this decorator type-annotated correctly?

Issue

from functools import wraps
from typing import Any, Callable


def timer(func: Callable[..., Any]) -> Callable[..., Any]:
    """Calculates the runtime of a function, and outputs it to logging.DEBUG."""

    @wraps(func)
    def wrapper(*args, **kwargs):
        start = perf_counter()
        value = func(*args, **kwargs)
        end = perf_counter()
        _logger = logging.getLogger(__name__ + "." + func.__name__)
        _logger.debug(" runtime: {:.4f} seconds".format(end - start))
        return value

    return wrapper

Solution

As of Python 3.10, you can easily preserve all type information through a decorator using ParamSpec:

from collections.abc import Callable
from functools import wraps
from typing import TypeVar, ParamSpec

T = TypeVar('T')
P = ParamSpec('P')

def timer(func: Callable[P, T]) -> Callable[P, T]:
    """Calculates the runtime of a function, and outputs it to logging.DEBUG."""

    @wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
        start = perf_counter()
        value = func(*args, **kwargs)
        end = perf_counter()
        _logger = logging.getLogger(__name__ + "." + func.__name__)
        _logger.debug(" runtime: {:.4f} seconds".format(end - start))
        return value

    return wrapper

(For python < 3.10, but prefer juanpa.arrivillaga’s answer to preserve information on the input types)

Indentation seems a bit off here, but otherwise, yes, the types aren’t incorrect. You could make this a bit more precise, though. The function you output has the same return type as the function taken as input, but you don’t note this.

In particular, you can say something like

from typing import Callable, TypeVar

T = TypeVar("T")
def timer(func: Callable[..., T]) -> Callable[..., T]:

It seems like you should be able to do the same with the args/kwargs, but I haven’t come across that case in my own typing experience, so I can’t say exactly how. EDIT – See this GitHub issue for more about typing those; it doesn’t seem possible (yet?).

I suppose you could also say

def timer(func: T) -> T:

but that doesn’t seem as useful.

Answered By – Nathan

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published