为什么 mypy `cast` 只在某些时候有效?

why does mypy `cast` only work some of the time?

首先我做了一个傻乎乎的小东西class

# cheese_helpers.py
class Cheese:
    pass

然后这件事发生了

# weird.py

import lxml
from typing import cast, List
import cheese_helpers

o: List[Any] = []
reveal_type(o) # builtins.List[Any] as expected

y = cast(List[cheese_helpers.Cheese], o)  
reveal_type(y)  # builtins.List[cheese_helpers.Cheese], as expected

# so far so good. And then:

z = cast(List[lxml.html.HtmlElement], o)  
reveal_type(z) # builtins.List[Any] ???????????????

如果你问我,最后一行应该是 List[lxml.html.HtmlElement]。奶酪也没有注释,而且效果很好。

我确定要使最后一行工作,我需要 get/make 一些 lxml 注释。但是我的 cast 被完全忽略了,这对我来说似乎很奇怪。我投射到 Cheese class 并且它起作用了。我投射到 HtmlElement class 但它没有。

我的问题是为什么?

您使用的是旧版本的 mypy 吗?当我尝试使用 mypy 0.630(pypi 上的最新版本)和他们的 git master 分支上的最新代码对您的代码进行类型检查时,我在所有三种情况下都得到了 builtins.list[Any] 的显示类型。

这个显示的类型希望更直观一些——问题是不幸的是 typeshed 上的 lxml 库没有可用的存根,这意味着 mypy 没有关于什么的信息html.HtmlElement 确实是。 (据 mypy 所知,这可能是一个 class、一个函数、一个变量、一个类型别名、一个 namedtuple...)

因此,它放弃并假定它具有 Any 的类型。

这也解释了为什么将 get_some_relevant_elements 的输出分配给 List[bool] 可以正常工作。 List[Any] 类型的变量理论上可以包含任何内容,包括布尔值——所以 可能 这是一个安全的赋值。


无论如何,如果您不喜欢这种行为,您的两个选择是:

  1. 接受 lxml 库没有类型 hints/is 纯动态类型,并设计您的代码,以便所有动态都包含在一个位置。当您从 XML 文件中提取信息时,(可选)验证它们并 return 您自己的自定义注释 classes。基本上,故意在代码库的动态部分和非动态部分之间设置一个屏障。

  2. 为 lxml 创建您自己的存根。这些存根不一定需要很复杂——为您需要的少数 classes 和方法创建初步存根可能就足够了。 (如果它们最终变得相当充实,如果您愿意的话,您也许可以开源它们并回馈社区。)