【问题标题】:dataclasses.Field doesn't resolve type annotation to actual typedataclasses.Field 不会将类型注释解析为实际类型
【发布时间】:2019-05-01 14:30:09
【问题描述】:

python 标准 dataclasses 模块的 Field 类的文档仅指定:

其记录的属性是:

  • [...]
  • type:字段的类型。

对我来说,这似乎意味着该字段将包含类型本身,而不仅仅是字符串形式的名称。

但是,它似乎只是按原样复制类型注释,使其毫无用处。

例子:

@dataclasses.dataclass 
class C: 
    c: 'C'

dataclasses.fields(C)[0].type # This returns the string 'C'
typing.get_type_hints(C)['c'] # This returns the class C, as expected

在使用PEP563 类型注解时甚至会系统地出现该问题。

这是数据类模块中的错误吗?这是预期的行为吗?如果是这样,我如何检索给定 Field 实例的类型对象?

【问题讨论】:

  • 到底是什么问题?您期望dataclass 模块如何处理该类型?它处理类型提示的方式与处理其他所有类型提示的方式完全相同。
  • 好吧,我希望它能够按照文档中的说明进行操作,即返回类型本身,而不是类型注释。

标签: python python-dataclasses


【解决方案1】:

这是故意的。在导入时解析类型提示是昂贵的,尤其是当from __future__ import annotations 已被用于禁用解析它们时。

最初,当您使用from __future__ import annotations 开关并为字段添加ClassVarInitVar 类型注释时,将PEP 563 添加到Python 3.7 会破坏数据类;这些将不会在这一点上得到解决,并且仍然是一个字符串。如果您明确使用字符串,这在 PEP 563 之前已经是一个问题,请参阅dataclasses issue #92。这变成了Python bug, #33453,一旦数据类进入 Python 3.7。

“父”项目attrs 启发了dataclassesalso had this issue to solve。 Łukasz Langa(大多数类型提示 peps 的合著者,包括 PEP 563)在那里指出:

好的,所以我尝试了上述方法,它似乎是一个核选项,因为它强制评估所有注释。这是我想通过from __future__ import annotations 避免的。

dataclasses 的作者 Eric Smith 在discussion on the pull request that fixed issue 33453 中表示:

我一直在研究这样做。我认为@ambv 的观点是由于在每个字段上调用 ​​eval 都会引入性能损失,而字符串注释的目的是消除性能损失。

此外,还有其他问题;您不能在导入时评估所有类型提示,而不是在它们使用前向引用时:

除了性能问题之外,在以下情况下(没有__future__ 语句和没有数据类),我在get_type_hints() 上收到一个错误,因为在调用get_type_hints()C 未定义。这是python/typing#508。请注意,在此示例中调用 get_type_hints() 的位置正是 @dataclass 将要运行的位置,并且需要调用精简后的 get_type_hints()

所以最后,dataclasses 所做的只是将字符串启发式应用于注释,并且不会为您加载它们。

要检索类型,只需在类本身上使用get_type_hints(),并将字段.name 属性作为结果的键:

resolved = typing.get_type_hints(C)
f = dataclasses.fields(C)[0]
ftype = resolved[f.name]

【讨论】:

  • 感谢您的详细解答!没有人想到在调用 dataclasses.fields 时解决注释,因为这是用户创建 Field 实例的唯一方法?它不会在导入时产生性能损失,并使 .type 字段保持一致。没有它,我看不到任何情况下这个字段可以做任何有用的事情......
  • @lovasoa:(抱歉,这让我的雷达下降的时间比我希望的要长):实现需要有dataclasses.Fields() 实例来生成类对象,其中包括__init__具有相同类型注释的方法,以及潜在的类变量和仅初始化字段。所以当 @dataclass 装饰器被执行时,Field() 实例也会被创建。
猜你喜欢
  • 2022-01-06
  • 1970-01-01
  • 2020-04-06
  • 2012-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-07
  • 1970-01-01
相关资源
最近更新 更多