字典和XML是两种常见的数据结构,Python处理字典非常方便,但对接某些老系统、SOAP接口或生成配置文件时,往往需要把字典转成XML格式。Python标准库虽然提供了xml.etree.ElementTree模块,但它只能从零构建XML节点,并没有直接的字典转换函数。本文介绍三种实现方式,从手写递归到第三方库,各有适用场景。

方法一:使用xml.etree.ElementTree手写递归函数
最原生的做法是利用标准库xml.etree.ElementTree,通过递归遍历字典,为每个键值对创建子节点。这种方式不需要安装任何第三方依赖,逻辑完全可控,适合对输出格式有严格要求、或者数据结构比较简单的场景。核心思路是:字典的键作为标签名,值作为标签的文本内容,嵌套字典则递归创建子节点。
下面是一个完整的实现示例,支持嵌套字典和列表两种结构。列表会被展开成多个同名子标签,这与XML表达重复元素的习惯一致:
import xml.etree.ElementTree as ET
def dict_to_xml(tag, d):
elem = ET.Element(tag)
for key, val in d.items():
child = ET.SubElement(elem, key)
if isinstance(val, dict):
# 嵌套字典,递归处理
child.extend(list(dict_to_xml(key, val)))
elif isinstance(val, list):
for item in val:
if isinstance(item, dict):
child.append(dict_to_xml(key, item))
else:
sub = ET.SubElement(child, key)
sub.text = str(item)
else:
child.text = str(val)
return elem
data = {
"name": "张三",
"age": 28,
"address": {"city": "北京", "zip": "100000"},
"hobbies": ["篮球", "阅读"]
}
root = dict_to_xml("person", data)
tree = ET.ElementTree(root)
ET.indent(tree, space=" ") # 格式化缩进,Python 3.9+
tree.write("output.xml", encoding="utf-8", xml_declaration=True)这种方式的优点是零依赖、灵活度高,可以随意控制标签命名、属性添加等细节。缺点是需要自己处理各种数据类型,比如None值、日期对象、嵌套列表等边界情况,代码量会随着需求增加而膨胀。如果只是偶尔转换一次简单结构,手写递归完全够用;如果数据结构复杂多变,建议直接用第三方库。
方法二:使用dicttoxml第三方库一行搞定
如果追求效率,dicttoxml库是最省事的选择。它通过pip安装后,一个函数调用就能完成转换,自动处理嵌套字典、列表、各种基本类型,甚至能通过type属性标注每个值的类型。安装命令很简单:
pip install dicttoxml
使用示例:
import dicttoxml
from xml.dom.minidom import parseString
data = {
"name": "李四",
"score": 95.5,
"passed": True,
"courses": ["数学", "英语"]
}
# 基本用法,返回bytes
xml_bytes = dicttoxml.dicttoxml(data)
print(xml_bytes.decode())
# 关闭type属性,输出更干净的XML
xml_bytes = dicttoxml.dicttoxml(data, attr_type=False)
# 格式化输出,带缩进更易读
dom = parseString(xml_bytes)
pretty_xml = dom.toprettyxml(indent=" ")
print(pretty_xml)默认情况下,dicttoxml会在每个节点上添加type属性,比如type="str"、type="int",方便反序列化时恢复类型,但很多系统并不接受这种格式,可以用attr_type=False参数关闭。这个库的另一个优点是对特殊值处理得当,None会转成空节点,布尔值会转成true或false的小写形式,符合XML的通用约定。缺点是项目近年的维护不太活跃,遇到非常定制的格式需求(比如指定根节点名、控制列表标签名)时能力有限,更复杂的需求可以考虑xmltodict的反向操作或者lxml。
方法三:进阶处理——属性、中文编码与自定义根节点
实际项目中,字典转XML经常遇到三个坑:一是需要把字典的某些键转成标签属性而不是子节点;二是中文乱码问题;三是根节点命名和命名空间的要求。先看属性的处理,ElementTree通过set方法给节点添加属性,可以在递归函数中约定特殊前缀,比如以下划线开头的键转成属性:
import xml.etree.ElementTree as ET
def dict_to_xml_with_attrs(tag, d):
elem = ET.Element(tag)
for key, val in d.items():
if key.startswith("_"):
# 键名以下划线开头,转成属性
elem.set(key[1:], str(val))
elif isinstance(val, dict):
elem.append(dict_to_xml_with_attrs(key, val))
else:
child = ET.SubElement(elem, key)
child.text = str(val)
return elem
data = {
"_id": "1001",
"_version": "2.0",
"title": "测试文档",
"content": {"body": "正文内容"}
}
root = dict_to_xml_with_attrs("doc", data)
print(ET.tostring(root, encoding="unicode"))关于中文编码,最常见的错误是直接用ET.tostring(root)输出bytes后按错误编码解码,导致中文变成乱码。正确做法有两个:写入文件时指定encoding="utf-8"并开启xml_declaration=True;如果只想要字符串预览,使用encoding="unicode"参数,返回的就是str类型。另外,某些老系统要求XML声明中写gbk编码,ElementTree也支持encoding="gbk",会自动处理字符转码。
如果需要自定义根节点和XML声明头,也可以手动拼接声明再加转换结果。对于需要严格校验或命名空间的场景,推荐用lxml,它完全兼容ElementTree的API,同时支持DTD校验、XSLT转换和命名空间定义,处理企业级XML需求更稳妥。
如何选择合适的方案
综合来看,三种方案各有定位。标准库手写递归适合格式严格定制、不想引入依赖的场景,代码可控但开发成本高;dicttoxml适合快速开发、格式要求通用的场景,一行代码解决问题但定制能力有限;lxml适合企业级应用,功能最强但学习成本略高。实际选型时还需要考虑反向转换需求,如果系统同时需要XML转字典,xmltodict库双向都支持,基于它的方案可能更统一。
最后提醒一点,无论用哪种方案,转换完成后都应该用ET.parse或lxml的解析函数验证输出是否为合法XML,并重点测试中文、特殊字符(如&、<)、空值等边界情况,避免在对接第三方系统时踩坑。
Python字典转XMLdicttoxmlxml.etree.ElementTree修改时间:2026-09-01 10:39:34