如何为装饰器的通用类型起别名

How to alias generic types for decorators

考虑

import unittest
from typing import *

T = TypeVar("T", bound=unittest.TestCase)

def decorate(func: Callable[[T], None]) -> Callable[[T], None]:
    def decorated_function(self: T) -> None:
        return func(self)
    return decorated_function

现在我什至有 a generator that creates these decorators 并且想要 shorthand 这些装饰器。我对存储装饰器的变量 variables 做什么样的类型(省略生成器的简化示例)。

my_decorate: Callable[[Callable[[T], None]], Callable[[T], None]] = decorate

这可行,但很笨重。所以问题是:

如何为这种类型起别名以避免必须编写完整签名?


不起作用的东西:

TD = Callable[[Callable[[T], None]], Callable[[T], None]]
my_decorate: TD[T] = decorator_variable

报错

error: Type variable "mypytest.T" is unbound
note: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class)
note: (Hint: Use "T" in function signature to bind "T" inside a function)

相比之下,我可以使用 TD[T] 作为函数的参数类型。

仅使用 my_decorate: TD = ... 会产生 --strict 错误

error: Missing type parameters for generic type "TD"

并且它不再检测 my_decorate 的错误应用。

这个呢?它比完整签名短:

import unittest
from typing import *

T = TypeVar("T", bound=unittest.TestCase)


def decorate(func: Callable[[T], None]) -> Callable[[T], None]:
    def decorated_function(self: T) -> None:
        return func(self)

    return decorated_function


decorator_variable: Callable[[Callable[[T], None]], Callable[[T], None]] = decorate

U = Callable[[T], None]
my_decorate: Callable[[U[T]], U[T]] = decorator_variable

在许多情况下,当 Callable 太有限时,请改用 Protocol

class TD(Protocol):
    """Type of any callable `(T -> None) -> (T -> None)` for all `T`"""
    def __call__(self, __original: Callable[[T], None]) -> Callable[[T], None]:
        ...

TD 不是 通用类型,因此不需要“填写”类型变量。可以直接作为注解使用:

my_decorate: TD = decorate

值得注意的是,TD.__call__ 仍然是一个通用的可调用对象,尽管 TD 不是通用的。它的 T 由每次调用的上下文根据需要填充。