Python 3.5类型注解变量无初值

Python 3.5 type annotated variable without initial value

我需要声明 "complex" 类型的全局变量,并且 不应 在导入时实例化。在Python 3.6+,我可以省略初始化,例如:

log: logging.Logger
pollset: select.poll

我需要使代码与 Python 3.5 兼容。我可以使用评论类型注释:

log = ...  # type: logging.Logger
pollset = ...  # type: select.poll

但我必须提供初始值。这在运行时不是问题,分配 None... 的初始值即可。但是其中任何一个都会触发 mypy 类型检查错误:

myprog.py:19: error: Incompatible types in assignment (expression has type "ellipsis", variable has type "Logger")

当然我可以使用Optional类型来允许初始化为None,但是类型检查会被削弱。例如,在代码的其他地方给变量赋值 None 是非法的,但不会被捕获。

是否有可接受的方法以与 Python 3.5 兼容的方式对变量进行强类型检查?

您可以采用的一种技术是创建类型为 Any 的虚拟变量,然后使用它而不是将变量设置为 ...None。例如:

from typing import Any

_bogus = None     # type: Any
log = _bogus      # type: logging.Logger
pollset = _bogus  # type: select.poll

但是,这个解决方案并不完美。使用变量注释,我们实际上避免了为这些变量创建赋值,因此在实例化之前尝试使用 log 会导致运行时出现 NameError。

但是,使用这种方法,我们会得到 None,这与我们声明的类型相矛盾。

也许这对你的用例来说是可以的,但如果不是,我们可以通过将它们粘贴在 if TYPE_CHECKING 块中来获得更接近变量注释行为的东西:

from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    _bogus = None     # type: Any
    log = _bogus      # type: logging.Logger
    pollset = _bogus  # type: select.poll

TYPE_CHECKING 变量在运行时始终为 False,但像 mypy 一样被 type-checkers 视为 True。

if False 也有效。这是一个 common-enough 约定,mypy 直接支持它作为使用 TYPE_CHECKING 的替代方法。)

根据PEP 484,分配None是正确的。

log = None  # type: logging.Logger

注意 mypy only allows this at class scope. However, you can declare a type and tell mypy to ignore the assignment itself (since mypy 0.700).

log = None  # type: logging.Logger  # type: ignore

此外,无论 Python 版本如何,您都可以使用 .pyi 存根文件。

# my_lib.pyi
log: logging.Logger

However, in non-stub code for versions of Python 3.5 and earlier there is a special case:

from typing import IO

stream = None  # type: IO[str]

Type checkers should not complain about this (despite the value None not matching the given type), nor should they change the inferred type to Optional[...] (despite the rule that does this for annotated arguments with a default value of None). The assumption here is that other code will ensure that the variable is given a value of the proper type, and all uses can assume that the variable has the given type.


PEP 484 None 的特殊处理已在 python/typing#61. Sadly, the type checkers (I tried mypy and pyre) don't implement this. The problem is further discussed in python/typing#81 中决定。

有一些解决方法可以使用

  1. None 或省略号转换为正确的动态类型。这将安抚 mypy 和 pyre。
self.process: subprocess.Popen = cast(subprocess.Popen, None)
self.process: subprocess.Popen = cast(subprocess.Popen, ...)
  1. 用注释抑制check,本质上和之前一样的效果,但是更好写。
self.process: subprocess.Popen = None  # type: ignore
  1. 声明变量 Optional[...],然后在每次访问时检查 None
self.process: Optional[subprocess.Popen] = None