Python 使用省略号或 None + float 类型提示未分配属性

Python leaving attribute unassigned with ellipsis or None + type hint of float

问题

我想声明一个实例属性,类型提示为 float,而不在初始化时赋值。该值在运行时分配,在实例初始化之后(也就是在 __init__ 之后)。

根据this answer,我可以将值设为None。但是,我的 IDE (PyCharm) 举起 PyTypeChecker 标志,说 Expected type 'float', got type 'None' instead.

我发现如果我将值设置为省略号(this answer 建议),又名值 = ...,PyCharm 不再抱怨。

这里的最佳做法是什么? 我应该使用 None 还是 ...


示例代码

class SomeClass:

    def __init__(self):
        """Initialize with an unassigned value."""

        # PyCharm complains here
        self._unassigned_val = None  # type: float

        # PyCharm doesn't complain here
        self._unassigned_val2 = ...  # type: float

    def called_during_runtime(self) -> None:
        """This gets called after __init__ has run."""
        self._unassigned_val = 1.0
        self._unassigned_val2 = 1.0

它在我的 IDE 中的样子:


版本信息

对于简单的情况,您实际上可以不在构造函数中执行任何操作:如果您这样做,mypy 和 Pycharm 将继续正确推断字段的类型:

class SomeClass:
    def called_during_runtime(self) -> None:
        self._unassigned_val = 1.0

当然,您需要承担确保此函数在运行时实际被调用的责任:如果您这样做,您的类型检查器不会警告您。

如果您的函数以足够复杂的方式为该字段赋值,您的类型检查器可能会窒息而不知道该怎么做。在这种情况下,如果您使用 Python 3.6 或更高版本,则可以使用 Variable annotations

class SomeClass:
    _unassigned_val: float

    def called_during_runtime(self) -> None:
        self._unassigned_val = 1.0

这与运行时的第一种方法完全相同。

如果您需要支持旧版本的 Python,您可以使用的另一种技术是创建一个 "bogus" 标记值,该值的类型为 Any,完全动态类型:

from typing import Any

BOGUS = object()  # type: Any

class SomeClass:
    def __init__(self) -> None:
        self._unassigned_val = BOGUS  # type: float

    def called_during_runtime(self) -> None:
        self._unassigned_val = 1.0

如果您改变主意并决定偏向类型检查器,请对其警告更加积极,您始终可以声明您的值可以是 float 或None:

from typing import Optional

class SomeClass:
    def __init__(self) -> None:
        self._unassigned_val = None  # type: Optional[float]

    def called_during_runtime(self) -> None:
        self._unassigned_val = 1.0

    def get_with_default(self, default: float) -> float:
        if self._unassigned_val is None:
            return default
        else:
            return self._unassigned_val

请注意,您可以在 if 语句和断言中组合使用 self._unassigned_val is not Noneself._unassigned_val is Noneisinstance(self._unassigned_val, float) 检查,让类型检查器有条件地缩小您的类型场.

最后一种方法是我个人所做的:我是类型检查器的粉丝,并且设置我的工具以非常积极地检测潜在问题。

关于省略号的最后一点说明:使用省略号作为占位符仅适用于存根和使用 Protocols method definitions or abstract classes 之类的东西时 - 基本上,在您永远不会实际使用您的值的情况下 field/method parameters/whatever 在运行时。