使用 TypeAlias 为 Callable 别名时出现 mypy 错误

问题描述 投票:0回答:1

假设我们有以下 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
中的错误还是我做错了什么?

python python-3.x mypy
1个回答
0
投票

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
(事实上,如果您进行更改,他们就会这样做)。

看起来您真正想要的是两件事之一:

  1. 您希望

    FuncSpec
    确实表示一个接受
    BaseContext
    和任意数量的附加参数的可调用函数。您不关心对其进行专门化或“捕获”这些附加参数的参数规范。

    如果是这样的话,你想要的是

    FuncSpec[...]
    :

    def reset_command_context(
        _func: FuncSpec[...],
        *,
        current: bool = False,
    ) -> None:
    

    这将通过书面的类型检查,但会给

    process_invite_unique
    process_ban
    类型
    None
    ,这可能不是你想要的。

  2. 您想要“捕获”

    _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]

© www.soinside.com 2019 - 2024. All rights reserved.