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

时间:2019-11-25 17:32:58

标签: python python-import typing mypy python-packaging

我有以下代码

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

模块xxxyyy提供相同的功能,但是功能的编码方式非常不同,并且基于不同的外部库(它们是程序包的可选依赖项)接受不同的输入类型。 / p>

很遗憾,mypy在抱怨:

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

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

1 个答案:

答案 0 :(得分:0)

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

这包括所有参数名称,因为关键字参数是一件很重要的事情:f1(xxxarg1=blah)对于第一个导入有效,但对后一个无效。

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

就我个人而言,我认为使两个函数的签名相同是最好的选择,因为这有助于最大程度地减少代码中存在细微错误的可能性/减少所需的测试量。

但是如果将API修改为相同是不可行的,则可以抑制该错误,或​​者尝试使mypy通过以下方式的组合来更精确地检查您的导入:

  • typing.TYPE_CHECKING变量,在运行时始终为False,但被mypy视为始终为真
  • ...以及--always-true/--always-false命令行标志,可以让mypy假定某个变量始终为true或false。

我知道总共可以使用三种方法:

方法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: ...也可以,尽管它会使代码更加隐秘。

要注意的重要一点是,此方法和类型忽略方法在类型安全性/不安全性方面完全相同。如果您想更清楚地了解自己在做什么,或者不惜一切代价避免忽略,则主要选择这种方法。

方法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