attrs 类变量却成了必填参数,检查 ClassVar 是否只在 TYPE_CHECKING 导入
Python 3.14 的延迟注解让未导入 ClassVar 的类能够定义,attrs 却可能将它当成普通字段。将 ClassVar 放回运行时导入,核对字段表与构造参数,别用默认值掩盖分类错误。
类里的 t 明明标了 ClassVar[str],调用 T() 却报 TypeError: T.__init__() missing 1 required positional argument: 't',先看 ClassVar 是否只在 if TYPE_CHECKING: 中导入。
Python 3.14 的默认延迟注解允许这类类定义走得更远:名字在运行时不存在,也未必当场报 NameError。attrs 取得的可能是带有未解析表达式的 ForwardRef,没把它识别成类变量,就会为 t 生成普通实例字段和初始化参数。attrs 26.1.0 的正式实现仍需注意这个组合。
类型标记需要在运行时可用
最直接的调整是正常导入 ClassVar:
from typing import ClassVar
import attrs
@attrs.define
class T:
t: ClassVar[str] = "shared"
instance = T()
assert "t" not in attrs.fields_dict(T)
这里的赋值只是给共享类属性提供一个值,真正决定它不成为字段的是 ClassVar 的识别。原来没有默认值、稍后才给类属性赋值的设计,可以保留;不要为了让 T() 不报错,随意给一个本应为类变量的实例字段加默认值。
TYPE_CHECKING 在普通运行中为假,放在其中的导入不会执行。它适合避免某些只供静态检查的依赖,不代表所有出现在注解中的名字都能安全地省略运行时绑定。attrs、序列化器和其他反射工具都可能实际检查注解。
检查的是字段归类,而不只是能否构造
调整后检查 attrs.fields_dict(T),确保 t 已从实例字段表中消失,再核对构造签名和应用的序列化结果。若之前错误地把它保存为实例数据,改注解不会自动清理数据库或已导出的字段。
启用 from __future__ import annotations 可能让这个具体例子走另一条字符串注解识别路径,但它同时改变整个模块的注解形式,不能把它当作保证所有反射工具兼容的开关。已经使用 future 注解的项目仍应检查运行时类型解析,而不是依赖某种字符串恰好被识别。
这也不同于字段别名时机或 slots 的描述符绑定问题。若 ClassVar 确实在运行时导入,而字段仍被纳入构造函数,应继续检查注解对象、装饰器和版本组合,不能一律删除构造参数。


