当装饰器更改通用返回类型时键入函数

Seb*_*ner 5 python generics mypy

这类似于装饰器更改返回类型时的键入函数,但这次使用通用返回类型

from typing import Generic, TypeVar, Generic, Callable, Any, cast

T = TypeVar('T')

class Container(Generic[T]):
    def __init__(self, item: T):
        self.item: T = item

def my_decorator(f: Callable[..., T]) -> Callable[..., Container[T]]:

    def wrapper(*args: Any, **kwargs: Any) -> Container[T]:
        return Container(f(*args, **kwargs))

    return cast(Callable[..., Container[T]], wrapper)

@my_decorator
def my_func(i: int, s: str) -> bool: ...

reveal_type(my_func) # Revealed type is 'def (*Any, **Any) -> file.Container[builtins.bool*]
Run Code Online (Sandbox Code Playgroud)

需要哪种 mypy 魔法来保持参数类型完整my_func

使用typing.Protocol看起来很有希望,但我不知道如何让它发挥作用。

fre*_*ech 7

使用Callable[..., T]是目前对此进行注释的最佳方式。

PEP 612 引入了ParamSpec,它的使用方式与 a 类似TypeVar,可以解决您的问题。目前计划用于 Python 3.10,并将支持旧版本使用typing_extensions

在那里你可以写:

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

def my_decorator(f: Callable[P, T]) -> Callable[P, Container[T]]:

    def wrapper(*args: Any, **kwargs: Any) -> Container[T]:
        return Container(f(*args, **kwargs))

    return cast(Callable[P, Container[T]], wrapper)
Run Code Online (Sandbox Code Playgroud)

mypy 对 PEP 612 的支持尚未完成:https ://github.com/python/mypy/issues/8645 。与 pytype(Google 的 python 类型检查器)相同。

Pyright(微软的 Python 类型检查器)和 Pyre(Facebook 的 Python 类型检查器)已经支持 PEP 612


cgl*_*cet 2

只是对已接受的答案和PEP612的最终状态进行快速更新,当前的形式如下:

def my_decorator(f: Callable[P, R]) -> Callable[P, Container[R]]:

    def wrapper(*args: P.args, **kwargs: P.kwargs) -> Container[R]:
        return Container(f(*args, **kwargs))

    return wrapper
Run Code Online (Sandbox Code Playgroud)

似乎在装饰函数中同时使用显式P.args和类型是强制性的。P.kwargs完整的代码示例是:

from typing import (
    Callable,
    Generic,
    ParamSpec,
    TypeVar,
)

T = TypeVar("T")

class Container(Generic[T]):
    def __init__(self, item: T):
        self.item: T = item

P = ParamSpec("P")
R = TypeVar("R")


def my_decorator(f: Callable[P, R]) -> Callable[P, Container[R]]:
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> Container[R]:
        return Container(f(*args, **kwargs))
    return wrapper

@my_decorator
def my_func(i: int, s: str) -> bool:
    ...
Run Code Online (Sandbox Code Playgroud)

这给出了预期的输入my_func

(function) def my_func(
    i: int,
    s: str
) -> Container[bool]
Run Code Online (Sandbox Code Playgroud)