pythonwrapperpython-typing

Python typing: pylance doesn't show input types


I'm trying to do "wrapper" class for any callable. Here is minimal example:

from typing import Any, TypeVar, Generic, Callable

ReturnType = TypeVar("ReturnType", bound=Any)
CallableType = Callable[..., ReturnT]


class Wrapper(Generic[ReturnType]):
    def __init__(self, callable_: CallableType[ReturnType]) -> None:
        self.callable_ = callable_

    def __call__(self, *args: Any, **kwargs: Any) -> ReturnType:
        return self.callable_(*args, **kwargs)


class Action:
    def __init__(self, title: str) -> None:
        """docstring"""
        self.title = title


wrapped = Wrapper(Action)

action = wrapped("title")

Pylance can understand, that action type is Action, but I can't see __init__ docsting and needed arguments:pylance help string

Desired behaviour:enter image description here

What do I do wrong and how can I fix it?


Solution

  • You've annotated your __call__ with Any. Pyright doesn't restrict these Any to more narrow types just based on your usage. You'll need to use a ParamSpec to capture the parameters of the callable you accept as the argument (similar how you capture the return type with a TypeVar).

    from typing import Any, TypeVar, ParamSpec, Generic, Callable
    
    Params = ParamSpec("Params")
    ReturnType = TypeVar("ReturnType")
    
    
    class Wrapper(Generic[Params, ReturnType]):
        def __init__(self, callable_: Callable[Params, ReturnType]) -> None:
            self.callable_ = callable_
    
        def __call__(self, *args: Params.args, **kwargs: Params.kwargs) -> ReturnType:
            return self.callable_(*args, **kwargs)
    
    
    class Action:
        def __init__(self, title: str) -> None:
            """docstring"""
            self.title = title
    
    
    wrapped = Wrapper(Action)
    
    action = wrapped("title")
    

    Note1: If you're using python<3.10 you'll need to import ParamSpec from typing_extensions.

    Note2: You don't need to pass a bound=Any to the TypeVar as that's (more or less) the default anyway. Passing bound=Any doesn't change anything nevertheless.