在 Python 3.7 中使用工厂函数生成注释类型时出现“typing.ClassVar”的问题

Problems with “typing.ClassVar” when using a factory function to generate annotation types in Python 3.7

我正在尝试使用工厂函数生成一些类型注释——专门针对 tuple 类型。我有一个运行良好的工厂版本(例如,它在 MyPy 中令人满意地编译、运行和检出):

import typing as tx
HomogenousTypeVar = tx.TypeVar('HomogenousTypeVar')
TupleTypeReturnType = tx.Type[tx.Tuple[HomogenousTypeVar, ...]]

def TupleType(length: int,
              tuptyp: tx.Type[HomogenousTypeVar] = str) -> TupleTypeReturnType:
    """ Create a type annotation for a tuple of a given type and length """
    assert length > 0
    return tx.Tuple[tuple(tuptyp for idx in range(length))]

… 用法如:

class Thing(object):

    __slots__: TupleType(2) = ('yo', 'dogg')
    other_fields: TupleType(4) = ('i', 'heard',
                                  'you', 'like')

    # etc, or what have you

... 然而,当我尝试添加对 typing.ClassVar 注释的支持时,我没有成功,它看起来像这样:

import typing as tx
HomogenousTypeVar = tx.TypeVar('HomogenousTypeVar')
TupleTypeReturnType = tx.Union[tx.Type[tx.Tuple[HomogenousTypeVar, ...]],
                               tx.Type[tx.ClassVar[tx.Tuple[HomogenousTypeVar, ...]]]]

def TupleType(length: int,
              tuptyp: tx.Type[HomogenousTypeVar] = str,
              clsvar: bool = False) -> TupleTypeReturnType:
    """ Create a type annotation for a tuple of a given type and length,
        specifying additionally whether or not it is a ClassVar """
    assert length > 0
    out = tx.Tuple[tuple(tuptyp for idx in range(length))]
    return clsvar and tx.ClassVar[out] or out

… 在此更改之后,代码甚至不会最初编译 - 它无法通过 typing 模块深处的 TypeError 进行编译:

TypeError: typing.ClassVar[typing.Tuple[~HomogenousTypeVar, ...]] is not valid as type argument

…随着错误的出现,我觉得有点像打电话进来;我的意思是,typing 中的 一切 不应该是某种形式的有效类型参数吗?

the typing source code related to ClassVar 中,文档字符串中提到了一些对其使用的限制——但这不是其中之一。有什么明显的我想念的吗?我以这种方式使用这个注释的尝试是不切实际的吗?我还可以尝试什么?

您确定您的原始代码片段确实使用 mypy 进行了类型检查吗?当我尝试使用 Mypy 0.620 或来自 github 的最新版本 运行 连接它时,我收到以下错误:

test.py:13: error: invalid type comment or annotation
test.py:13: note: Suggestion: use TupleType[...] instead of TupleType(...)
test.py:14: error: invalid type comment or annotation
test.py:14: note: Suggestion: use TupleType[...] instead of TupleType(...)

我也无法重现您使用 ClassVar 代码时遇到的错误——当我尝试 运行ning 时,我收到以下错误:

test.py:4: error: Invalid type: ClassVar nested inside other type
test.py:6: error: Incompatible default for argument "tuptyp" (default has type "Type[str]", argument has type "Type[HomogenousTypeVar]")
test.py:12: error: Invalid type alias
test.py:13: warning: Returning Any from function declared to return "Union[Type[Tuple[HomogenousTypeVar?, ...]], Type[Tuple[HomogenousTypeVar?, ...]]]"
test.py:15: error: Name 'Thing' is not defined
test.py:16: error: Revealed type is 'Any'

您确定您实际上是在 运行ning mypy,而不只是 运行ning 代码吗?例如。如果你只 运行 python3 test.py,你基本上跳过了所有的类型检查(除了输入模块中内置的一些最低限度的完整性检查)。

如果你想对你的代码进行类型检查,你需要 pip-install mypy 和 运行 python3 -m mypy test.py.


在任何情况下,所有这些错误消息都是预期的行为——mypy(和任何其他符合 PEP 484 的类型检查器)只能静态分析您的代码并且不会尝试运行 或分析任何工厂 functions/any 您可以尝试编写的类型提示生成函数。

因此,如果您希望符合 PEP 484 的工具能够分析您的代码,那么不幸的是,您将无法将生成的类型提示与 ClassVars 一起使用——它们不能 understand/interpret您原来的类型提示集,添加 ClassVars 肯定无济于事。

如果你想生成类型提示,我能想到的唯一可行的选择是在 Python 之上发明某种微型语言或宏系统,当 运行 时,将生成 Python 代码。然后,您将 运行 并检查生成的代码而不是宏化 Python 语言。

但我真的不建议这样做 -- 这是一个非常脆弱的 hack。


更广泛地说,每当您开始 运行遇到这些与类型相关的限制时,我认为这表明您的代码太复杂了。我会考虑简化您的代码,或者(如果那不可能)切换到 Haskell 或 Idris 这样的语言,这样您就可以使用更具表现力(尽管更复杂)的类型系统。

例如,在这种情况下,您试图概括 Tuple 类型——这让我推断您的代码库包含许多不同类型的元组。

这让我觉得有点可疑——我会考虑将其中一些元组转换为常规 类 或(如果您仍然需要类似元组的功能)namedtuple. Dataclasses (which are new as of Python 3.7 ) 在这里也很方便。

这些解决方案还有助于使您的代码更具可读性——您现在可以为每种不同类型的元组赋予具体的名称和含义。

或者,如果您只有几种不同类型的元组,但到处都使用这些元组,您可以尝试使用 type aliases,这样您就不必重复重新键入相同的(长) 一遍又一遍地输入。例如。而不是做:

def foo(x: Tuple[int, int, int, int]) -> None: ...

...你可以这样做:

IpAddress = Tuple[int, int, int, int]

def foo(x: IpAddress) -> None: ...