【问题标题】:How to fix mypy error when using click's pass_context使用click的pass_context时如何修复mypy错误
【发布时间】:2022-01-24 18:14:52
【问题描述】:

我正在使用click 构建命令行应用程序。我正在使用mypy 进行类型检查。

但是,使用 @pass_context 将上下文传递给函数按预期工作,但 mypy 失败并出现错误:

error: Argument 1 to "print_or_exit" has incompatible type "str"; expected "Context"

我不明白为什么。以下是重现此 mypy 错误的 MWE:

import click
from typing import Optional

@click.pass_context
def print_or_exit(ctx: click.Context, some_txt: Optional[str] = "") -> None:
    if ctx.params.get("exit_", False):
        exit(1)
    print(some_txt)

@click.command(context_settings=dict(help_option_names=["-h", "--help"]))
@click.option("--exit","-e", "exit_", is_flag=True, help="exit")
@click.pass_context
def main(ctx: click.Context, exit_: bool) -> None:
    print_or_exit("bla")


if __name__ == "__main__":
    main()

使用参数-e运行脚本,则脚本存在,不打印到终端;当省略 -e 时,脚本会打印到终端,因此一切都按预期工作。

那么,为什么 mypy 会失败?

【问题讨论】:

  • Did ctx arg int print_or_exitstrclick.Context 吗?
  • 您的 mypy 是否可以访问 click 模块的存根库?如果不是,则无法知道@click.passcontext 通过更改其参数来修改以下函数。
  • @FrankYellin 是的,据我所知,我使用的是 click 版本 > 8,它具有内置存根

标签: python mypy


【解决方案1】:

我看过sources of clickdecorators.py

F = t.TypeVar("F", bound=t.Callable[..., t.Any])
FC = t.TypeVar("FC", t.Callable[..., t.Any], Command)


def pass_context(f: F) -> F:
    """Marks a callback as wanting to receive the current context
    object as first argument.
    """

    def new_func(*args, **kwargs):  # type: ignore
        return f(get_current_context(), *args, **kwargs)

    return update_wrapper(t.cast(F, new_func), f)

所以,函数 pass_context 返回相同的类型 (-> F) ,接收参数 (f: F)。因此,mypy 期望您在 print_or_exit 中传递两个参数。

我认为最好的解决方案是尽可能明确地传递ctx。这样做的好处——您可以轻松地在测试中模拟 ctx 以获取 print_or_exit 函数。所以,我建议这个代码:

import click
from typing import Optional

def print_or_exit(ctx: click.Context, some_txt: Optional[str] = "") -> None:
    if ctx.params.get("exit_", False):
        exit(1)
    print(some_txt)

@click.command(context_settings=dict(help_option_names=["-h", "--help"]))
@click.option("--exit","-e", "exit_", is_flag=True, help="exit")
@click.pass_context
def main(ctx: click.Context, exit_: bool) -> None:
    print_or_exit(ctx, "bla")


if __name__ == "__main__":
    main()

它按预期工作并通过了mypy

【讨论】:

  • 这看起来像是注释中的错误。 pass_context 不应被注释为返回相同的类型。
  • 理想的解决方法是使用typing.ParamSpec(并从typing_extensions 获取旧版Python 版本的反向移植ParamSpec),但如果没有ParamSpec,最好的选择可能只是Any。跨度>
  • @Eugenij 我接受这个作为答案,因为这是目前唯一的选择(尽管我不喜欢它)。
  • @user69453:它应该看起来很像 typing.Concatenate docs 中的 with_lock 示例装饰器,但使用的是 click.Context 而不是锁。
  • 该功能是超级新的,不过,mypy doesn't even have proper support for it yet。如果您尝试使用“正确”的注释,目前,mypy throws an error 因为它还不理解 Concatenate。在没有适当的 mypy 支持的情况下,Any 可能是目前要走的路。
猜你喜欢
  • 2021-05-29
  • 1970-01-01
  • 2022-12-13
  • 1970-01-01
  • 2021-12-31
  • 2021-11-17
  • 2018-07-07
  • 2019-09-21
  • 2020-10-03
相关资源
最近更新 更多