在 Python class 中存储和断言类型

Store and assert type in a Python class

PyCharm CE 2019.2.3 警告我 class,因为我存储了 list 数据以及值数据 type。我试图将其还原为本质:

class Data:
    def __init__(self, length, data_type):
        assert isinstance(length, int) and length >= 0
        assert isinstance(data_type, type)  # commenting this line out removes the warning
        self._data = [None] * length
        self._data_type = data_type

    def set_field(self, index, value):
        assert isinstance(value, self._data_type)  # commenting this line out removes the warning
        assert isinstance(index, int)
        self._data[index] = value

我在索引上收到警告:

Unexpected type(s):
(int, type)

Possible types:
(int, None)
(slice, Iterable[None])

Inspection Info: This inspection detects type errors in function call expressions. Due to dynamic dispatch and duck typing, this is possible in a limited but useful number of cases. Types of function parameters can be specified in docstrings or in Python 3 function annotation.

进一步评论:

我的问题:我在存储和断言类型方面做错了吗?该警告是否有任何原因?如果没有,您是否看到了删除它的便捷方法?

这里的问题有两个:

  • PyCharm 在通过打字正确处理元 classes 方面有一些盲点(在撰写本文时)。因此,如果没有额外的 "hints",它目前无法推断出 value 的正确类型。
  • 因为您使用 None 初始化了您的列表 PyCharm 将其视为包含 Nones 的列表。

让我们从元class问题开始:

您通过检查 data_type 是否是 type 的实例(classes 的默认元 class)来检查 data_type 是否是 class。然后检查 value 是否是 class 的实例。没关系。

但是 PyCharm 假定您的 data_typetype(这是正确的)但是在 isinstance(value, self._data_type) 之后它还假定 valuetype(这是不正确的 - 它应该是 _data_type 类型)。因此,仅通过将 assert isinstance(...)data_typevalue PyCharm 一起使用将无法推断出 value!

的正确类型

所以这可能是 PyCharm 中的错误或缺失的功能 - 或者 PyCharm 用来确定类型的任何库。


第二个问题是,通过用 None PyCharm 初始化 _data 将推断 _data 的类型为 List[None](包含的列表无)。

所以如果 PyCharm 推导出除 Any(可以分配给任何东西)或 None(这将是列表内容的预期类型)之外的任何东西 value 它将导致警告。

即使有:

    def set_field(self, index, value):
        assert isinstance(value, int)  # <-- difference
        assert isinstance(index, int)
        self._data[index] = value

警告会来的。


此时你有两个选择:

  • 忽略警告。
  • 使用成熟的类型提示(如果目标 Python 版本允许)。

如果你想使用类型提示,你可以使用:

from typing import List, Optional, TypeVar, Generic, Type

T = TypeVar('T')

class Data(Generic[T]):

    _data: List[Optional[T]]
    _data_type: Type[T]

    def __init__(self, length: int, data_type: Type[T]):
        self._data = [None] * length
        self._data_type = data_type

    def set_field(self, index: int, value: T):
        if isinstance(value, self._data_type):
            self._data[index] = value
        raise TypeError()

注意:我已经完全删除了断言。如果参数没有预期的类型或值,TypeErrorValueError 会提供更多信息。然而,在大多数情况下,文档 and/or 类型提示可以充分替换 asserts 以及 Python 中的 TypeErrors(至少在使用 IDE 或使用 mypy 时).