(我几乎可以肯定问题的措辞没有意义,但我还没有找到更好的措辞。)
我有以下代码需要进行类型检查:
from collections.abc import Callable
from typing import Generic, TypeVar, TypeVarTuple
C = TypeVar("C")
T = TypeVar("T")
Ts = TypeVarTuple("Ts")
class Decorator1(Generic[T]):
def __init__(self, /, type: type[T]) -> None:
super().__init__()
def __call__(self, function: Callable[[C, T], None], /) -> Callable[[C, T], None]:
return function
class DecoratorNa(Generic[T]):
# Q: how to prevent "T" from becoming "object" here?
def __init__(self, /, *type: type[T]) -> None:
super().__init__()
def __call__(self, function: Callable[[C, T], None], /) -> Callable[[C, T], None]:
return function
class DecoratorNb(Generic[*Ts]):
def __init__(self, /, *types: *Ts) -> None:
super().__init__()
# Q: how to convert "*Ts" from "type[int], type[str]" to "int, str" here?
def __call__(
self, function: Callable[[C, *Ts], None], /
) -> Callable[[C, *Ts], None]:
return function
class MyClass:
# This is given and supposed to type-check:
@Decorator1(int)
def print_number(self, number: int) -> None: ...
@Decorator1(str)
def print_string(self, string: str) -> None: ...
# # This is supposed to error:
# @Decorator1(int)
# def print_another_string(self, string: str) -> None: ...
# This is supposed to type-check:
@DecoratorNa(int, str)
def print_number_and_string_a(self, number: int, string: str) -> None: ...
# This is supposed to type-check:
@DecoratorNb(int, str)
def print_number_and_string_b(self, number: int, string: str) -> None: ...
Decorator
的构造函数在运行时接受一种(或多种)类型(比较PySide6.QtCore.Slot()
),并且修饰函数应该接受该类型的一个(或多个)参数。
这对于使用 type[T]
的单一类型效果很好,但我无法为多种类型找到相同的东西。我尝试过
*type: type[T]
但评估结果为
T = object
。对于
TypeVarTuple
,我最终不知道如何将
*Ts
转换为
ParamSpec
之类的东西。我知道
typing.get_args
,但我不确定在类型检查时应该如何应用它。
DecoratorNa(int, str)
) 的调用表达式,目前正在提交,并且处于修订的最后阶段1,但截至今天,还没有任何具体的
TypeVarTuple
转变。但是,如果您愿意将语法从
__call__
(
DecoratorNa(int, str)
) 切换为
__getitem__
(
DecoratorN[int, str]
) 表达式,以下内容可能适合您(mypy Playground、Pyright Playground) :
...
from types import MethodType
...
class DecoratorN(Generic[*Ts]):
def __init__(
self, function: Callable[[Any, *Ts], None], /
) -> None:
super().__init__()
self._function = function
def __get__(self, instance: Any, owner: type[Any], /) -> Callable[[*Ts], None]:
return MethodType(self._function, instance) # type: ignore[return-value]
class MyClass:
# This is given and supposed to type-check:
@Decorator1[int]
def print_number(self, number: int) -> None: ...
@Decorator1[str]
def print_string(self, string: str) -> None: ...
@Decorator1[int] # error
def print_another_string(self, string: str) -> None: ...
# This is supposed to type-check:
@DecoratorN[int, str]
def print_number_and_string(self, number: int, string: str) -> None: ...
mc: MyClass = MyClass()
mc.print_number(1)
mc.print_number("") # error
mc.print_string("")
mc.print_string(1) # error
mc.print_number_and_string(1, "")
mc.print_number_and_string("", 1) # error
这里,方括号允许正确解释为 DecoratorN[*Ts]
类型的类型参数化,然后用于强制修饰函数具有相同的参数类型 (
*Ts
)。如果您不喜欢拥有
DecoratorN
实例的想法,并且需要保留原始实例方法类型(如问题中的示例所示),您可以执行以下操作(不完整的实现):
if typing.TYPE_CHECKING:
class DecoratorN(Generic[*Ts]): ...
else:
class _DecoratorNType:
def __getitem__(self, /, *types_):
return type(self)(*types_)
def __call__(self, function):
return function
DecoratorN = _DecoratorNType()