没有初始值的Python 3.5类型带注释的变量

时间:2019-06-13 15:43:35

标签: python typechecking mypy

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

log: logging.Logger
pollset: select.poll

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

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

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

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

我当然可以使用Optional类型来允许初始化为None,但随后类型检查会被削弱。例如,将None的值分配给代码中其他位置的变量是非法的,但不会被捕获。

是否存在一种可接受的方式来以与Python 3.5兼容的方式对变量进行强类型检查?

3 个答案:

答案 0 :(得分:1)

您可以采用的一种方法是创建类型为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这样的类型检查器将其视为True。

(也可以执行if False。这是一个常见的约定,mypy直接支持了这一点,以代替使用TYPE_CHECKING。)

答案 1 :(得分:1)

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

log = None  # type: logging.Logger

请注意,mypy仅在类范围内允许这样做。但是,您可以声明类型告诉mypy忽略赋值本身(since mypy 0.700)。

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

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

# my_lib.pyi
log: logging.Logger

  

但是,在Python 3.5及更早版本的非存根代码中,有一种特殊情况:

from typing import IO

stream = None  # type: IO[str]
     

类型检查器不应对此抱怨(尽管值None不匹配给定类型),也不应将推断的类型更改为Optional [...](尽管规则对具有默认值的带注释的参数执行此操作无)。这里的假设是其他代码将确保为变量赋予适当类型的值,并且所有使用都可以假定变量具有给定类型。


答案 2 :(得分:0)

python/typing#61中决定了None对PEP 484的特殊处理。可悲的是,类型检查器(我尝试过mypy和pyre)没有实现这一点。该问题将在python/typing#81中进一步讨论。

有可以使用的解决方法

  1. None(或省略号)设置为正确的动态类型。这样可以安抚mypy和pyre。
self.process: subprocess.Popen = cast(subprocess.Popen, None)
self.process: subprocess.Popen = cast(subprocess.Popen, ...)
  1. 用注释抑制检查,其效果与以前基本相同,但写起来更好。
self.process: subprocess.Popen = None  # type: ignore
  1. 声明变量Optional[...],然后在每次访问时检查None
self.process: Optional[subprocess.Popen] = None