"sys.version_info" 的类型是什么?

What is the type of "sys.version_info"?

如何向以下函数添加类型注释?

import sys

def f(info):
    return info.major

f(sys.version_info)

使用pyannotate建议应该是:

import sys

def f(info):
    # type: (sys.version_info) -> int
    return info.major

f(sys.version_info)

然而 mypy 抱怨:

$ mypy test.py
test.py:10: error: Variable "sys.version_info" is not valid as a type
test.py:12: error: sys.version_info? has no attribute "major"```

sys.version_info的类型好像是它自己:

>>> import sys
>>> type(sys.version_info)
<class 'sys.version_info'>

它看起来像一个 NamedTuple,但如果它被这样注释:

from typing import NamedTuple

VersionInfo = NamedTuple(
    'sys.version_info', [
        ('major', int),
        ('minor', int),
        ('micro', int),
        ('releaselevel', str),
        ('serial', int)
    ]
)

import sys

def f(info):
    # type: (VersionInfo) -> int
    return info.major

f(sys.version_info)

mypy 检查仍然失败:

$ mypy test.py
test.py:25: error: Argument 1 to "f" has incompatible type "_version_info"; expected "sys.version_info@9"

为了找到某些标准库函数的类型,您应该查看 Typeshed,标准库的类型提示存储库 + select 第 3 方模块。

在这种情况下,我们可以看到 sys.version_infoannotated as being of type sys._version_info,这是一个伪造的,仅合成类型 class subclass 是 Tuple[int, int, int, str, int].

您也可以通过 运行 mypy 针对以下程序自行验证:

import sys
reveal_type(sys.version_info)

这会产生以下输出——请注意最后的回退:

 Revealed type is 'Tuple[builtins.int, builtins.int, builtins.int, builtins.str, builtins.int, fallback=sys._version_info]'

因此,要注释您的函数,您有多种选择。首先,您可以使用这种仅进行类型检查的 class——您只需要确保在运行时永远不会导入它 it/reference。

如果您使用的是类型注释语法(例如 # type: (sys._version_info) -> int),这个问题已经为您解决了。毕竟,注释中的代码片段永远不会在运行时进行评估。

但是如果你想使用 Python-3 样式类型注释,你可以这样做:

from __future__ import annotations
import sys

def f(info: sys._version_info) -> int:
    return info.major

f(sys.version_info)

__future__ 导入基本上使上面的程序等同于下面的程序——所有类型提示都被视为字符串,因此不需要在运行时求值:

import sys

def f(info: "sys._version_info") -> int:
    return info.major

f(sys.version_info)

或者,如果您想显式导入没有 sys 模块命名空间的类型,您也可以这样做:

from __future__ import annotations
from typing import TYPE_CHECKING
import sys

# Doing just 'if False:' also works
if TYPE_CHECKING:
    from sys import _version_info as VersionInfo

def f(info: VersionInfo) -> int:
    return info.major

f(sys.version_info)