attrs 字段转换器读到 alias=None,检查默认别名的生成时机
attrs 转换器在默认别名生成前读取字段时会拿到 None,应采用回调前解析修复并区分自动别名与显式别名。
用 attrs.define(field_transformer=...) 遍历字段时,私有字段和普通字段的 alias 是 None,但类构造完成后再用 attrs.fields() 查询,又能读到正确的构造参数名。这种前后差异会影响依赖别名建立映射的转换器,不代表字段没有注解或类定义失败。
旧流程先调用转换器,之后才生成默认别名。例如 _value 在实例上仍叫 _value,构造参数默认叫 value;显式设置的别名早已存在,自动生成的别名却要等回调结束。只在类定义完成后检查字段表,容易遗漏转换器当时拿到的不完整信息。
attrs 26.1.0 包含调整:先解析已有字段的默认别名,再调用转换器;转换器新增的字段仍有后续补解析。该版本也提供 alias_is_default,用于区分自动生成的别名和用户显式指定的别名。原来用 alias is None 判断“用户未指定”的转换器,需要同步调整。
python -m pip install "attrs==26.1.0"
下面检查回调阶段与类完成后的字段,避免只验证实例能创建:
import attrs
seen = {}
def inspect_fields(cls, fields):
for field in fields:
seen[field.name] = (field.alias, field.alias_is_default)
return fields
@attrs.define(field_transformer=inspect_fields)
class Item:
_value: int = attrs.field(default=0)
label: str = attrs.field(default="", alias="display_label")
assert seen["_value"] == ("value", True)
assert seen["label"] == ("display_label", False)
item = Item(value=7, display_label="sample")
assert item._value == 7 and item.label == "sample"
assert attrs.fields(Item)._value.alias == "value"
验收应保留真实转换器的返回值,检查它有没有重命名、删除或新建字段。若通过 Attribute.evolve(name=...) 改名,还应检查最终构造函数签名:自动别名会随名称更新,显式别名会保留,二者不应套同一推导规则。
迁移前若转换器依赖 None 来生成自己的默认别名,应将意图写成明确的判断,并用 alias_is_default 决定是否覆盖。不要把所有字段的别名都重新设为去下划线后的名称,那会覆盖用户显式选择的参数名。
这个变化处理的是字段回调所见的别名状态。必填字段与默认字段的顺序、验证器、转换器和静态类型检查各有自己的规则;出现其他类构造异常时,仍需沿对应错误判断。


