使用TypeVar /泛型和中间类型变量的装饰器的python类型提示

问题描述

我试图找出一种很好的方式来为装饰器进行类型提示。这是一个简化/最小的示例。装饰器称为@stringify_input,它将装饰函数的输入转换为字符串:

@stringify_input
def two_things(x: str) -> float:
    return float(x + x)

assert two_things(1) == 11.0


# implementation with no hints:
def stringify_input(fn):
    def inner(thing: int):
        return fn(str(thing))
    return inner

说我事先不知道装饰函数返回什么,但是我知道装饰器不会更改它。当前,未经装饰的函子返回一个float,因此经过装饰的函子也应该返回。我已经知道如何使用TypeVar来做到这一点:

# types check
T = TypeVar("T")

def stringify_input(fn: Callable[[str],T]) -> Callable[[int],T]:
    def inner(thing: int) -> T:
        return fn(str(thing))
    return inner

这是我的问题:那些Callable[....]提示很难读,如果有名字,它们会更容易理解(我的IRL装饰器更复杂)。所以我试试这个:

StringFn = Callable[[str],T]
IntFn = Callable[[int],T]

def stringify_input(fn: StringFn) -> IntFn:
    def inner(thing: int) -> T:
        return fn(str(thing))   # Returning Any from function declared to return 'T'
    return inner

但是没有输入check:

Returning Any from function declared to return 'T'

为什么mypy不满意?我实际上只是提取了两个表达式!关于TypeVar的工作方式?

解决方法

TypeVar已拆分为不同范围内的同名几个类型变量。声明所有表达式使用相同范围的T

# TypeVar scope equals function scope
def stringify_input(fn: StringFn[T]) -> IntFn[T]:
    def inner(thing: int) -> T:
       ...

通常在全局范围内声明TypeVars their substitution happens in a fixed scope。例如,可以在两个函数中使用相同的TypeVar来表示单独的实际类型替换。

T = TypeVar('T')

def foo(a: T): ...
def bar(b: T): ...

foo("bar!")   # T -> str
bar(b"foo!")  # T -> bytes

通过将类型移到函数之外,三个作用域对T具有不同的含义。

StringFn = Callable[[str],T]    # T1
IntFn = Callable[[int],T]       # T2

def stringify_input(fn: StringFn) -> IntFn:
    def inner(thing: int) -> T:  # T3
        ...
    return inner

由于StringFnIntFn都具有免费的TypeVar,因此可以由另一个上下文的TypeVar对其进行参数化。通过功能范围的T 对其进行参数设置,以显示StringFnIntFninner的所有参数都由相同的{{1 }}:

T