26. 在字典与 XML 之间进行转换
本文将重点介绍 [xml2dict] 模块,该模块可实现:
- 字符串 XML 转换为字典:
- 字典转换为字符串 XML;
在 jSON 出现之前,Web 服务的响应通常采用 XML(eXtended 标记语言)。 此外,这些Web服务的协议通常是SOAP(简单对象处理协议)。SOAP是一种基于Web的HTTP协议的协议。 目前(2020年),Web服务多采用REST(表征状态转移)类型。我们研究过的Web服务虽不属于上述任何类型,但无疑更接近REST而非SOAP。 不过,我更倾向于称其为“自由”或“未知”类型,因为它们并未完全遵循REST的所有规则。
我们将演示如何轻松地将我们的 jSON 客户端/服务器架构转换为 XML 客户端/服务器架构。只需使用 [xmltodict] 模块即可。
首先,我们在 Python 终端中安装该模块:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install xmltodict
Collecting xmltodict
Using cached xmltodict-0.12.0-py2.py3-none-any.whl (9.2 kB)
Installing collected packages: xmltodict
Successfully installed xmltodict-0.12.0
完成安装后,我们将通过一个示例来了解该模块的功能:

脚本 [xml_01] 如下:
from collections import OrderedDict
import xmltodict
# xmltodict.parse 用于将 XML 传递给字典。该字典必须具有根节点
# 生成的词典类型为 OrderedDict
# xmltodict.unparse,用于从词典转换为 XML
def ordereddict2dict(ordered_dictionary) -> dict:
…
def transform(message: str, dictionary: dict):
# 日志
print(f"\n{message}-------")
print(f"dictionnaire={dictionary}")
# 词典 -> xml
xml1 = xmltodict.unparse(dictionary)
print(f"xml={xml1}")
# xml -> OrderedDict
ordereddict_dictionary1 = xmltodict.parse(xml1)
print(f"ordereddict_dictionary1={ordereddict_dictionary1}")
# OrderedDict -> 字典
print(f"dict_dictionary1={ordereddict2dict(ordereddict_dictionary1)}")
# 测试 1
transform("test 1", {"nom": "séléné"})
# 测试 2
transform("test 2", {"famille": {"père": {"prénom": "andré"}, "mère": {"prénom": "angèle"}, "nom": "séléné"}})
# 测试 3
transform("test 3", {"famille": {"nom": "séléné", "père": {"prénom": "andré"}, "mère": {"prénom": "angèle"},
"hobbies": ["chant", "footing"]}})
# 测试 4
transform("test 4", {'réponse': {
'错误: ['方法 GET 仅需参数 [marié, enfants, salaire]','缺少参数 [marié]',
'缺少参数 [enfants]', '缺少参数 [salaire]']}})
# 测试 5
transform("test 5", {'réponse': {
'result': {'id': 0, '已婚': '是', '子女': 2, '工资': 50000, '税款': 1384, '减免': 384, '附加费': 0,
'减免': 347, '税率': 0.14}}})
# 测试 6
transform("test 6", {"root": {'liste': ["un", "deux", "trois"]}})
# 测试 7
transform("test 7", {"root": {'liste': [{"un": [10, 11]}, {"deux": [20, 21]}, {"trois": [30, 31]}]}})
- 第 14-25 行:函数 [transform] 接收待写入的文本 [message] 和一个字典 [dictionary];
- 第 16 行:显示消息;
- 第17行:显示接收到的字典;
- 第19-20行:将该词典转换为字符串XML并显示。负责此操作的方法是[xmltodict.unparse];
- 第21-23行:将前面的字符串XML转换为字典并显示该字典。 能够实现此操作的方法是 [xmltodict.parse]。该方法生成的不是 [dict] 类型的字典,而是 [OrderedDict] 类型的字典(第 1 行);
- 第24-25行:利用(尚未编写)的方法[ordereddict2dict],将获得的[OrderedDict]类型转换为[dict]类型。该方法采用递归方式工作。 如果字典中的某些值属于类型 [OrderedDict, list],则检查这些集合的值是否也属于类型 [OrderedDict]。如果是,则将其转换为类型 [dict]。 需要注意的是,方法 [xmltodict.parse] 不会生成任何类型为 [dict] 的字典;
在检查缺失的函数之前,先查看结果以了解需要查找的内容:
测试 1(第 28-29 行)产生以下结果:
test 1-------
dictionnaire={'nom': 'séléné'}
xml=<?xml version="1.0" encoding="utf-8"?>
<nom>séléné</nom>
ordereddict_dictionary1=OrderedDict([('nom', 'séléné')])
dict_dictionary1={'nom': 'séléné'}
- 第 2 行:待测试的字典。需要注意一个重要事项:方法 [xml2dict.unparse] 要求字典采用 {‘键’ : 值} 的形式,其中 [valeur] 随后可以是字典、列表或简单类型;
- 第3-4行:由字典生成的字符串 XML。其前缀为 [<?xml version="1.0" encoding="utf-8"?>\n],该前缀通常是 XML 文件的第一行;
- 第5行:通过方法[xml2dict.parse]生成的类型[OrderedDict],该方法接收前面的字符串XML作为参数;
- 第6行:通过将方法[ordereddict2dict]应用于前一种类型而获得的类型[dict]的字典。这里出现了第2行中的原始字典;
所有其他测试均遵循相同模式,应能帮助您理解如何从字典转换为字符串 XML,以及如何从该字符串 XML 转换回原始字典。
其他测试结果如下:
test 2-------
dictionnaire={'famille': {'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'nom': 'séléné'}}
xml=<?xml version="1.0" encoding="utf-8"?>
<famille><père><prénom>andré</prénom></père><mère><prénom>angèle</prénom></mère><nom>séléné</nom></famille>
ordereddict_dictionary1=OrderedDict([('famille', OrderedDict([('père', OrderedDict([('prénom', 'andré')])), ('mère', OrderedDict([('prénom', 'angèle')])), ('nom', 'séléné')]))])
dict_dictionary1={'famille': {'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'nom': 'séléné'}}
test 3-------
dictionnaire={'famille': {'nom': 'séléné', 'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'hobbies': ['chant', 'footing']}}
xml=<?xml version="1.0" encoding="utf-8"?>
<famille><nom>séléné</nom><père><prénom>andré</prénom></père><mère><prénom>angèle</prénom></mère><hobbies>chant</hobbies><hobbies>footing</hobbies></famille>
ordereddict_dictionary1=OrderedDict([('famille', OrderedDict([('nom', 'séléné'), ('père', OrderedDict([('prénom', 'andré')])), ('mère', OrderedDict([('prénom', 'angèle')])), ('hobbies', ['chant', 'footing'])]))])
dict_dictionary1={'famille': {'nom': 'séléné', 'père': {'prénom': 'andré'}, 'mère': {'prénom': 'angèle'}, 'hobbies': ['chant', 'footing']}}
test 4-------
dictionnaire={'réponse': {'erreurs': ['Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]', 'paramètre [marié] manquant', 'paramètre [enfants] manquant', 'paramètre [salaire] manquant']}}
xml=<?xml version="1.0" encoding="utf-8"?>
<réponse><erreurs>Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]</erreurs><erreurs>paramètre [marié] manquant</erreurs><erreurs>paramètre [enfants] manquant</erreurs><erreurs>paramètre [salaire] manquant</erreurs></réponse>
ordereddict_dictionary1=OrderedDict([('réponse', OrderedDict([('erreurs', ['Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]', 'paramètre [marié] manquant', 'paramètre [enfants] manquant', 'paramètre [salaire] manquant'])]))])
dict_dictionary1={'réponse': {'erreurs': ['Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]', 'paramètre [marié] manquant', 'paramètre [enfants] manquant', 'paramètre [salaire] manquant']}}
test 5-------
dictionnaire={'réponse': {'result': {'id': 0, 'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'décôte': 384, 'surcôte': 0, 'réduction': 347, 'taux': 0.14}}}
xml=<?xml version="1.0" encoding="utf-8"?>
<réponse><result><id>0</id><marié>oui</marié><enfants>2</enfants><salaire>50000</salaire><impôt>1384</impôt><décôte>384</décôte><surcôte>0</surcôte><réduction>347</réduction><taux>0.14</taux></result></réponse>
ordereddict_dictionary1=OrderedDict([('réponse', OrderedDict([('result', OrderedDict([('id', '0'), ('marié', 'oui'), ('enfants', '2'), ('salaire', '50000'), ('impôt', '1384'), ('décôte', '384'), ('surcôte', '0'), ('réduction', '347'), ('taux', '0.14')]))]))])
dict_dictionary1={'réponse': {'result': {'id': '0', 'marié': 'oui', 'enfants': '2', 'salaire': '50000', 'impôt': '1384', 'décôte': '384', 'surcôte': '0', 'réduction': '347', 'taux': '0.14'}}}
test 6-------
dictionnaire={'root': {'liste': ['un', 'deux', 'trois']}}
xml=<?xml version="1.0" encoding="utf-8"?>
<root><liste>un</liste><liste>deux</liste><liste>trois</liste></root>
ordereddict_dictionary1=OrderedDict([('root', OrderedDict([('liste', ['un', 'deux', 'trois'])]))])
dict_dictionary1={'root': {'liste': ['un', 'deux', 'trois']}}
test 7-------
dictionnaire={'root': {'liste': [{'un': [10, 11]}, {'deux': [20, 21]}, {'trois': [30, 31]}]}}
xml=<?xml version="1.0" encoding="utf-8"?>
<root><liste><un>10</un><un>11</un></liste><liste><deux>20</deux><deux>21</deux></liste><liste><trois>30</trois><trois>31</trois></liste></root>
ordereddict_dictionary1=OrderedDict([('root', OrderedDict([('liste', [OrderedDict([('un', ['10', '11'])]), OrderedDict([('deux', ['20', '21'])]), OrderedDict([('trois', ['30', '31'])])])]))])
dict_dictionary1={'root': {'liste': [{'un': ['10', '11']}, {'deux': ['20', '21']}, {'trois': ['30', '31']}]}}
Process finished with exit code 0
- 第 23 行和第 27 行揭示了一个重要点:
- 第 23 行:字典 [result] 中键对应的值是数字;
- 第26行:字典[ordereddict_dictionary1]中键对应的值是字符串。这是库[xmltodict]的一个缺陷。 其方法 [parse] 仅生成字符串。这很容易理解:
- 第 25 行:生成该字典所依据的字符串 XML。 在此字符串中,并未标明 XML 标签内封装的数据类型。[xmltodict.parse] 采取了最合理的做法:将所有内容均以字符串形式保留在生成的词典中。 还有其他与 [xmltodict] 类似的库,其中标签中会标明封装的数据类型。例如,我们可以找到标签 [<enfants type=’int’>2</enfants>];
- 由此带来的后果是,当使用由模块 [xmltodict] 生成的字典时,必须了解其封装的数据类型,才能将“str”类型转换为数据的实际类型;
现在让我们关注方法 [ordereddict2dict],它将类型 [OrderedDict] 转换为类型 [dict]:
# xmltodict.parse 用于从 XML 转换到词典。词典必须有一个根
# 生成的词典类型为 OrderedDict
# xmltodict.unparse,用于从词典转换为 XML
def check(value):
# 如果值类型为 OrderedDict,则将其转换
if isinstance(value, OrderedDict):
value2 = ordereddict2dict(value)
# 如果值是列表类型,则将其转换
elif isinstance(value, list):
value2 = list2list(value)
else:
# 当前为简单类型,而非集合
value2 = value
# 返回新值
return value2
def list2list(liste: list) -> list:
# 新列表
newliste = []
# 处理参数列表中的元素
for value in liste:
# 将 value 添加到新列表中
newliste.append(check(value))
# 返回新列表
return newliste
def ordereddict2dict(ordered_dictionary: OrderedDict) -> dict:
# OrderedDict -> 字典(递归处理)
newdict = {}
for key, value in ordered_dictionary.items():
# 将值存储到新字典中
newdict[key] = check(value)
# 返回字典
return newdict
- 第 30 行:函数 [ordereddict2dict] 接收类型 [OrderedDict] 作为参数;
- 第 32 行:类型为 [dict] 的字典,该字典将在第 37 行由函数返回;
- 第 33 行:遍历字典 [ordered_dictionary] 中的所有 (键, 值) 元组;
- 第 35 行:在新字典中,保留键 [key],但其关联值不是 [value],而是 [check(value)]。 函数 [check(value)] 的作用是:如果 [value] 是一个集合,则查找所有类型为 [OrderedDict] 的元素,并将它们转换为类型 [dict];
方法 [check] 定义在第 5-16 行:
- 第 5 行:由于不知道 [value] 的类型,因此无法写成 [value : type];
- 第 7-8 行:若 [value] 的类型为 [OrderedDict],则递归调用刚刚注释掉的函数 [ordereddict2dict];
- 第9-11行:另一种可能的情况是[value]是一个列表。在这种情况下,第11行会调用第19-27行中的函数[list2list];
- 第12-14行:最后一种情况是,[value] 不是集合,而是一种简单类型。 函数 [check] 与函数 [ordereddict2dict] 和 [list2list] 一样,都是递归函数。众所周知,此时必须始终考虑递归终止的情况。第 12-14 行即为这种情况;
- 第 16 行:无论是否被递归调用,函数 [check] 都会生成一个 [valeur2] 值,该值必须替换第 5 行中的参数 [value];
第19-27行定义的方法[list2list]处理作为参数传递的列表。它将遍历该列表,并将其中所有类型为[OrderedDict]的值替换为类型[dict]。
- 第21行:函数将创建的新列表;
- 第23-25行:遍历列表中的所有[value]值,并将它们替换为[check(value)]。 该值 [value] 本身可能包含类型为 [list] 或 [OrderedDict] 的元素。它们将由递归函数 [check] 正确处理;