假设我们有以下 Python 3 代码:
from typing import Callable, Concatenate, ParamSpec, TypeAlias
class BaseContext:
...
Params = ParamSpec("Params")
def reset_command_context(
_func: Callable[Concatenate[BaseContext, Params], None],
*,
current: bool = False,
) -> None:
...
@reset_command_context
def process_invite_unique(ctx: BaseContext, unique: bool) -> None:
...
@reset_command_context
def process_ban(ctx: BaseContext) -> None:
...
然后我运行静态类型检查器:
~ $ pyright test.py
WARNING: there is a new pyright version available (v1.1.330 -> v1.1.330.post0).
Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest`
0 errors, 0 warnings, 0 informations
~ $ mypy test.py
Success: no issues found in 1 source file
看来我们都还好。但是,让我们为
TypeAlias
定义 Callable
,然后使用该别名在 _func
装饰器中注释 reset_command_context
参数:
FuncSpec: TypeAlias = Callable[Concatenate[BaseContext, Params], None]
def reset_command_context(
_func: FuncSpec,
*,
current: bool = False,
) -> None:
...
运行静态类型检查器现在显示不同的结果。
pyright
还可以,但是mypy
怪:
~ $ mypy test.py
test2.py:20: error: Argument 1 to "reset_command_context" has incompatible type "Callable[[BaseContext, bool], None]"; expected "Callable[[BaseContext, VarArg(Any), KwArg(Any)], None]" [arg-type]
test2.py:20: note: This is likely because "process_invite_unique" has named arguments: "ctx". Consider marking them positional-only
test2.py:25: error: Argument 1 to "reset_command_context" has incompatible type "Callable[[BaseContext], None]"; expected "Callable[[BaseContext, VarArg(Any), KwArg(Any)], None]" [arg-type]
test2.py:25: note: This is likely because "process_ban" has named arguments: "ctx". Consider marking them positional-only
Found 2 errors in 1 file (checked 1 source file)
这是
mypy
中的错误还是我做错了什么?
FuncSpec
是泛型类型别名,因为您在其定义中使用了类型变量 (ParamSpec
)。
无论您在何处使用
FuncSpec
,您都可以使用参数规范对其进行参数化 --- 可以是具体的一个,也可以是另一个 ParamSpec
。例如,
FuncSpec[[int, str, float]]
,FuncSpec[...]
,或FuncSpec[Params]
.如果您没有显式参数化它,您的类型检查器会将其视为
FuncSpec[Any]
。
在这种情况下,mypy 拒绝您的代码是正确的。您暗示
reset_command_context
接受 FuncSpec[Any]
,它是可调用的,允许使用 BaseContext
位置参数和 任意数量的附加位置和关键字参数 进行调用。 process_invite_unique
和 process_ban
都不能与该签名绑定,因为它们不能接受任意更多的参数。您需要给他们 ctx: BaseContext, *args: Any, **kwargs: Any
签名,以便他们按照书面方式使用 reset_command_context
(事实上,如果您进行更改,他们就会这样做)。
看起来您真正想要的是两件事之一:
您希望
FuncSpec
确实表示一个接受 BaseContext
和任意数量的附加参数的可调用函数。您不关心对其进行专门化或“捕获”这些附加参数的参数规范。
如果是这样的话,你想要的是
FuncSpec[...]
:
def reset_command_context(
_func: FuncSpec[...],
*,
current: bool = False,
) -> None:
这将通过书面的类型检查,但会给
process_invite_unique
和process_ban
类型None
,这可能不是你想要的。
您想要“捕获”
_func
的参数规范,并在reset_command_context
的签名中重新使用它
假设您希望
reset_command_context
返回一个可调用的,其尾部参数规范为 _func
,并且 BaseContext
从前面弹出。这可以使用现有的 Params
类型变量表示为
def reset_command_context(
_func: FuncSpec[Params],
*,
current: bool = False,
) -> Callable[Params, None]:
这将为
process_invite_unique
提供类型 Callable[[bool], None
和 process_ban
类型 Callable[[], None]
。