拓冰建站拓冰建站
首页 / 资讯中心 / 正文

【Bug已解决】special_tokens, ‘list‘ object has no attribute ‘keys‘ 解决方案

【Bug已解决】special_tokens, list object has no attribute keys 解决方案一、现象长什么样在配置或加载 tokenizer 的special_tokens时代码突然抛AttributeError: list object has no attribute keys而且报错往往发生在 transformers 内部不是你自己的代码stack 类似File .../tokenization_utils_base.py, in _add_tokens for k in special_tokens.keys(): AttributeError: list object has no attribute keys最迷惑的是你明明按文档传了 special tokens为什么它要调.keys()原因是某些入口期望special_tokens是一个{token_type: token_string}的字典如{bos_token: bos}而你传了一个 list如[bos, eos]。当代码对这个值调.keys()时list 没有这个方法于是炸。这个 bug 的本质是类型契约不一致transformers 里add_special_tokens接受 dict但add_tokens加普通词表 token接受 list很多人把两者搞混对 special tokens 也传了 list触发.keys()报错。二、背景transformers 的 tokenizer 有两个相关但签名不同的接口add_tokens(list)往词表追加普通 token参数是 list of str。add_special_tokens(dict)设置特殊 token参数是{角色: token}的字典例如{bos_token: bos, eos_token: eos}。内部实现里add_special_tokens拿到参数后会遍历special_tokens.keys()来逐个角色设置。如果你错误地传了一个 list比如从某个配置里读出来的[...]for k in special_tokens.keys()就变成for k in [..].keys()→AttributeError: list object has no attribute keys。常见触发场景从 JSON/YAML 配置读取 special tokens格式写成了数组而非对象把add_tokens的习惯套到 special tokens 上某些版本文档示例用了 list但代码实际要 dict版本漂移自己封装的build_special_tokens函数返回了 list却被传给期望 dict 的入口。下面用可运行代码复现期望 dict 收到 list 调.keys()报错的机制。三、根因根因一句话special_tokens的消费者如add_special_tokens期望一个 dict 并调用.keys()但调用方传入了 listlist 没有.keys()方法于是AttributeError: list object has no attribute keys。三个具体失配类型契约混淆add_special_tokens要 dict误传 list与add_tokens的 list 签名混淆。配置格式错误special tokens 在配置文件里写成数组而非对象。版本漂移文档/旧代码用 list新代码要 dict。四、最小可运行复现用纯 Python 模拟期望 dict 的函数收到 list 调.keys()报错from dataclasses import dataclass from typing import Union, List, Dict def add_special_tokens(special_tokens: Dict[str, str]): 模拟 transformers 内部遍历 special_tokens.keys()。 for role in special_tokens.keys(): print(f设置 {role} - {special_tokens[role]}) def main(): # 错误传了 list 而非 dict bad [bos, eos, pad] try: add_special_tokens(bad) except AttributeError as e: print(复现到报错:, e) # 正确传 dict good {bos_token: bos, eos_token: eos, pad_token: pad} add_special_tokens(good) if __name__ __main__: main()运行会先打印复现到报错: list object has no attribute keys再打印正确路径——正是 special tokens 类型错配的本质。五、解决方案第一层最小直接修复最立竿见影的修复统一 special tokens 的表示永远用 dict{角色: token}传给add_special_tokens若你手头只有 list先把它映射成 dict。def list_to_special_dict(tokens: list, rolesNone): 修复把 list 形式的 special tokens 转成 {角色: token} 字典。 if roles is None: # 约定顺序bos, eos, pad, unk ... 按需调整 roles [bos_token, eos_token, pad_token, unk_token] if len(tokens) len(roles): raise ValueError(token 数量超过预设角色数量) return {roles[i]: t for i, t in enumerate(tokens)} def main(): raw [bos, eos, pad] spec list_to_special_dict(raw) print(转为 dict:, spec) # 现在可安全传给 add_special_tokens(spec) if __name__ __main__: main()第一层修复让 special tokens 在传给add_special_tokens前一定是 dict.keys()调用不再报错。六、解决方案第二层结构性改进把special tokens 必须 dict收口成一个SpecialTokens规范类型在传给任何 transformers 入口前做类型归一化与校验list 自动转 dict非法类型直接报错。from dataclasses import dataclass, field from typing import Dict, List, Union dataclass class SpecialTokens: data: Dict[str, str] field(default_factorydict) classmethod def coerce(cls, value: Union[Dict, List, SpecialTokens]): if isinstance(value, SpecialTokens): return value if isinstance(value, dict): return cls(datavalue) if isinstance(value, list): # 自动映射为默认角色 roles [bos_token, eos_token, pad_token, unk_token, mask_token] if len(value) len(roles): raise ValueError(list 过长无法映射到默认角色) return cls(data{roles[i]: v for i, v in enumerate(value)}) raise TypeError(fspecial_tokens 必须是 dict 或 list收到 {type(value)}) def as_dict(self) - Dict[str, str]: return self.data def main(): # 无论上游给 list 还是 dict都归一化为 dict for raw in ([bos, eos], {eos_token: eos}): spec SpecialTokens.coerce(raw) print(归一化结果:, spec.as_dict()) if __name__ __main__: main()第二层的关键是SpecialTokens.coerce调用方不必关心上游给的是 list 还是 dict统一收口成 dict且对非法类型显式报错彻底消除.keys()的 AttributeError。七、解决方案第三层断言 / CI 守护加 pytest 守护(1) dict 输入原样保留(2) list 输入被自动转 dict(3) 非 list/dict 类型必须被拒(4) 转后的 dict 可被.keys()安全调用。import pytest class SpecialTokens: def __init__(self, data): self.data data classmethod def coerce(cls, v): if isinstance(v, dict): return cls(v) if isinstance(v, list): roles [bos_token, eos_token, pad_token] return cls({roles[i]: x for i, x in enumerate(v)}) raise TypeError(must be dict or list) def test_dict_passthrough(): s SpecialTokens.coerce({bos_token: b}) assert bos_token in s.data.keys() def test_list_coerced(): s SpecialTokens.coerce([b, e]) assert s.data {bos_token: b, eos_token: e} def test_invalid_type_rejected(): with pytest.raises(TypeError): SpecialTokens.coerce(123) def test_keys_callable(): s SpecialTokens.coerce([b]) assert callable(getattr(s.data, keys, None)) if __name__ __main__: pytest.main([__file__, -q])CI 里test_list_coercedtest_keys_callable通过就能保证 special tokens 永远是 dict、.keys()永远可用从根上防住list object has no attribute keys回归。八、排查清单遇到list object has no attribute keys且与 special_tokens 相关时按此顺序查定位报错里的.keys()调用基本锁定某个把 special_tokens 当 dict 处理的入口。检查你传的是 list 还是 dictadd_special_tokens要 dictadd_tokens才要 list——别混。检查配置文件格式special tokens 在 JSON/YAML 里应是对象{}而非数组[]。确认 transformers 版本老文档可能用 list新代码要 dict按当前版本签名为准。用 SpecialTokens.coerce 兜底入口处统一归一化list 自动转 dict。打印实际类型print(type(special_tokens))确认传入的是 dict。不要手动拼 special_tokens_map让add_special_tokens(dict)去填tokenizer.special_tokens_map别自己构造可能错类型的结构。九、小结special_tokens, list object has no attribute keys这个报错根因不在 transformers 内部有 bug而在类型契约不一致special tokens 的消费者如add_special_tokens期望 dict 并调用.keys()但调用方误传了 list把add_tokens(list)的习惯套到了 special tokens 上或配置文件把 special tokens 写成了数组。list 没有.keys()于是 AttributeError。修复三层第一层永远用{角色: token}的 dict 传给add_special_tokenslist 先映射成 dict第二层用SpecialTokens.coerce在入口统一归一化list 自动转 dict、非法类型显式报错第三层用 pytest 断言dict 原样保留、list 自动转 dict、.keys()永远可用。记住special tokens 是 dict普通 tokens 是 list——add_special_tokens与add_tokens的签名别搞混。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门