xmltodict.unparse() 返回空字符串或报错的根本原因是输入数据不符合xml根结构要求:顶层必须是单个键值对(如{"root": {...}}),不能是列表、多键字典、none或无根扁平结构;需手动包裹根节点,且属性须用@前缀、文本用#text标识。

xmltodict.unparse() 为什么返回空字符串或报错 TypeError: unhashable type: 'dict'
根本原因不是字典结构不对,而是传给 unparse() 的顶层数据不是合法的“XML根结构”:它要求顶层必须是单个键值对(即一个根元素),不能是列表、纯字符串、None,也不能是多个并列 key 的 dict。常见错误是直接把从 JSON 解析来的扁平 dict 或嵌套 list 丢进去。
- ✅ 正确输入:
{"root": {"name": "Alice", "age": "30"}}—— 单个根 key"root" - ❌ 错误输入:
[{"item": "a"}, {"item": "b"}](list)、{"a": 1, "b": 2}(多根)、{"root": [...]}但内部 list 没用@attrs或#text约定好语义 - ⚠️ 注意:
unparse()不会自动帮你加根;如果原始数据没根,得先包一层,比如{"data": your_dict}
怎么处理含属性、文本混合的节点(比如 <tag id="123">text</tag>)
xmltodict 对属性和文本的约定是硬编码的:@ 开头的 key 表示属性,#text 表示标签内纯文本。不按这个规则写,生成的 XML 就会漏属性或把文本当子元素。
- 要生成
<person id="100" type="user">John</person>,字典得写成:{"person": {"@id": "100", "@type": "user", "#text": "John"}} - 如果同时有子元素和文本(XML 不推荐但允许),
#text和其它 key 可共存,但需确保解析器能接受这种混合 - 属性名不能含空格或特殊字符;
@是固定前缀,不能换成$或省略
中文、特殊字符、CDATA 怎么安全输出
unparse() 默认用 UTF-8 编码,但不会自动转义或包裹 CDATA。如果你的文本含 、<code>&、中文,只要输入是 Python str(非 bytes),它就能正常编码输出 —— 前提是调用时显式指定 encoding 和 full_document。
- 中文没问题:
unparse({"root": "你好"}, encoding="utf-8")→ 正确输出带<?xml version="1.0" encoding="utf-8"?>的字符串 - 想禁用 XML 声明?传
full_document=False;否则默认为True,开头必带声明行 - 需要 CDATA?xmltodict 本身不支持;得手动替换,比如生成后用
.replace(" 预留占位,再正则替换内容 —— 这属于业务层补救,不是 <code>unparse()职责
性能差、内存暴涨?小心深层嵌套和大字符串
unparse() 是纯 Python 实现,递归构建字符串。当字典嵌套超 100 层,或某个 #text 值是几 MB 的 base64 字符串时,它会明显变慢,甚至触发 RecursionError 或吃光内存。
立即学习“Python免费学习笔记(深入)”;
- 深度嵌套:Python 默认递归限制约 1000 层,可用
sys.setrecursionlimit(2000)临时放宽,但治标不治本;建议先 flatten 或分段生成 - 大文本:避免把整个文件内容塞进
#text;改用外部引用或流式写入(这时就不该用unparse(),该换xml.etree.ElementTree) - 对比实测:10 万行简单记录用
unparse()生成耗时约 1.2s;同数据用ElementTree构建再tostring()约 0.3s —— 量大时别图省事
真正难的从来不是“怎么调用”,而是判断该不该用 xmltodict.unparse():它适合小到中等、结构清晰、属性/文本规则明确的场景;一旦涉及流、校验、命名空间或性能敏感,就该切回原生 XML 库。









