26. 从字典到 XML 及反向转换
在此,我们将探讨 [xml2dict] 模块,它允许您进行以下转换:
- 将 XML 字符串转换为字典:
- 字典转换为 XML 字符串;
在 JSON 出现之前,Web 服务的响应通常采用 XML(扩展标记语言)格式。此外,这些 Web 服务的协议通常是 SOAP(简单对象访问协议)。 SOAP 是一种基于 Web HTTP 协议的协议。目前(2020 年),Web 服务大多属于 REST(表征状态转移)类型。我们所研究的 Web 服务虽不属于上述任何一种类型,但无疑比 SOAP 更接近 REST。尽管如此,我更倾向于将其归类为“自由”或“未知”类型,因为它们并未完全遵循 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] 脚本如下:
- 第 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] 方法要求字典采用 {‘key’: value} 的形式,其中 [value] 可以是字典、列表或简单类型;
- 第 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] 的库,其中封装数据的类型会在标签中指定。例如,可能会遇到 [<children type='int'>2</children>] 这样的标签;
- 其结果是,当使用 [xmltodict] 模块生成的字典时,必须知道其封装的数据类型,才能将 ‘str’ 类型转换为实际的数据类型;
现在让我们仔细看看 [ordereddict2dict] 方法,它将 [OrderedDict] 类型转换为 [dict] 类型:
- 第 30 行:[ordereddict2dict] 函数将 [OrderedDict] 类型作为参数;
- 第 32 行:函数将在第 37 行返回的 [dict] 类型的字典;
- 第 33 行:遍历 [ordered_dictionary] 字典中的所有 (key, value) 元组;
- 第 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] 函数都会返回一个值 [value2],该值必须替换第 5 行中的 [value] 参数;
第 19–27 行定义的 [list2list] 方法处理作为参数传递的列表。它将遍历该列表,并将其中发现的任何 [OrderedDict] 值替换为 [dict] 类型。
- 第 21 行:函数将创建的新列表;
- 第 23–25 行:遍历列表中的所有 [value] 值,并将其替换为 [check(value)] 的值。该 [value] 本身可能包含 [list] 或 [OrderedDict] 类型的元素。递归函数 [check] 会正确处理这些元素;