条件导入的 mypy“不兼容导入”错误

rmm*_*mmr 5 python type-hinting python-import mypy python-packaging

我有以下代码

try:
    from mypackage.optional.xxx import f1, f2
except ImportError:
    from mypackage.optional.yyy import f1, f2
Run Code Online (Sandbox Code Playgroud)

模块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]")
Run Code Online (Sandbox Code Playgroud)

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

Mic*_*x2a 6

这里的问题是 mypy 对这两个导入有点挑剔——在满足 mypy 之前,这两个库需要具有相同的 API。

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

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

就个人而言,我认为使两个函数的签名相同是最好的选择,因为它有助于最大限度地减少您的代码出现细微错误的机会/减少您需要执行的测试量。

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

  • typing.TYPE_CHECKING变量,它总是在运行时假,但被视为是始终mypy真
  • ...也许还有--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
Run Code Online (Sandbox Code Playgroud)

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

方法 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
Run Code Online (Sandbox Code Playgroud)

这样做if False: ... else: ...也有效,尽管它使代码更加神秘。

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

方法 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
Run Code Online (Sandbox Code Playgroud)

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