mypy "Incompatible import" 条件导入错误

mypy "Incompatible import" error for conditional imports

我有以下代码

try:
    from mypackage.optional.xxx import f1, f2
except ImportError:
    from mypackage.optional.yyy import f1, f2

模块 xxxyyy 提供相同的功能,但功能的编码非常不同,接受不同的输入类型并基于不同的外部库(它们是我的包的可选依赖项) .

不幸的是mypy正在抱怨:

error: Incompatible import of "f1" (imported name has type "Callable[[Arg(Any, 'yyyarg1')], Any]", local name has type "Callable[[Arg(Any, 'xxxarg1')], Any]")

我该如何解决这个问题?有条件地导入相同功能(即具有相似签名的相同函数名称)的最佳方法是什么?

这里的问题是 mypy 对这两个导入有些挑剔——这两个库需要有相同的 API 才能满足 mypy。

这包括任何参数名称,因为关键字参数是一回事:执行 f1(xxxarg1=blah) 将适用于第一次导入,但不适用于后者。

(针对此特定情况的解决方法是 (a) 使您的参数具有相同的名称,(b) 使用仅在 Python 3.8+ 中可用的 positional-only arguments,或者 ( c) 在您的参数名称前加上两个下划线,这是声明参数仅位置的 mypy 特定方式——但此策略适用于所有 Python 版本。)

就我个人而言,我认为让两个函数的签名相同是最好的选择,因为它有助于最大限度地减少您的代码对您需要进行的测试量进行微妙 bugs/reduce 的可能性。

但是如果将您的 API 修改为相同不可行,您可以抑制错误或尝试让 mypy 通过以下组合更精确地对您的导入进行类型检查:

  • typing.TYPE_CHECKING 变量,在 运行 时始终为 False,但被 mypy
  • 视为始终为 true
  • ...也许还有 --always-true/--always-false 命令行标志,它让您告诉 mypy 假设某个变量始终为真或假。

总的来说,我知道您可以采用三种不同的方法:

方法 1:在第二次导入时抑制任何错误

首先,如果这两个库具有几乎相同的 API,并且您不关心两者之间的任何细微差异,一种策略可能是只键入忽略后者的导入,这将使 mypy 抑制任何源自的错误在最后一行。

所有其他行的类型检查将不受影响,这意味着 mypy 将继续假定 f1f2 是从 xxx 导入的。

try:
    from mypackage.optional.xxx import f1, f2
except ImportError:
    from mypackage.optional.yyy import f1, f2  # type: ignore

这种类型忽略选项可能是最实用的方法。

方法 2:明确选择第一个导入,忽略第二个

或者,如果您不喜欢忽略任何内容,您可以通过以下方式让 mypy 完全忽略该导入:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    # Ignored at runtime, but not by mypy
    from mypackage.optional.xxx import f1, f2
else:
    # Ignored by mypy, but not at runtime
    try:
        from mypackage.optional.xxx import f1, f2
    except ImportError:
        from mypackage.optional.yyy import f1, f2

if False: ... else: ... 也可以,尽管它会使代码更加神秘。

需要注意的一件重要事情是,这种方法和类型忽略方法在类型 safety/unsafety 上完全相同。如果您想更明确地说明自己在做什么或想不惜一切代价避免被忽略,则主要会选择这种方法。

方法 3:类型检查两个变体

第三个也是最后一个选项是 运行 mypy 两次,每个库使用 --always-true/--always-false 标志一次。这将是最安全和最严格的选择。

例如,您可以这样做:

from typing import TYPE_CHECKING

# Actual runtime logic
if not TYPE_CHECKING:
    # Ignored by mypy, but not at runtime
    try:
        from mypackage.optional.xxx import f1, f2
        USES_XXX = True
    except ImportError:
        from mypackage.optional.yyy import f1, f2
        USES_XXX = False

# For the benefit of mypy
if TYPE_CHECKING:
    if USES_XXX:
        from mypackage.optional.xxx import f1, f2
    else:
        from mypackage.optional.yyy import f1, f2

...然后 运行 mypy --always-true=USES_XXX your_codemypy --always-false=USES_XXX your_code