DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

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 决定是否覆盖。不要把所有字段的别名都重新设为去下划线后的名称,那会覆盖用户显式选择的参数名。

这个变化处理的是字段回调所见的别名状态。必填字段与默认字段的顺序、验证器、转换器和静态类型检查各有自己的规则;出现其他类构造异常时,仍需沿对应错误判断。