Python使用yield递归解析XML时为何会跳过递归逻辑?
问题描述
给定如下XML文件:
<?xml version="1.0" encoding="UTF-8"?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:okp="okapi-framework:xliff-extensions" xmlns:its="http://www.w3.org/2005/11/its" xmlns:itsxlf="http://www.w3.org/ns/its-xliff/" its:version="2.0"> <file original="temp/file_conversion/tmp4a9kn6bn/69502fea-751c-4c3c-a38a-4fce9e13ebde.txt" source-language="en" target-language="ar" datatype="x-text/plain" okp:inputEncoding="UTF-8"> <body> <trans-unit id="1idhasofh" xml:space="preserve"> <source xml:lang="en">foo<bpt id="0"><bar></bpt><Instruction><ept id="0"><crow></ept><grande></source> <target xml:lang="ar">foo<bpt id="0"><bar></bpt><Instruction><ept id="0"><crow></ept><grande></target> </trans-unit> </body> </file> </xliff>
编写了如下基于yield的XML解析函数:
from xml.etree import ElementTree as ET def parse_xml(ele: ET.Element): tag = ele.tag if not isinstance(tag, str) and tag is not None: return t = ele.text if t: yield t for e in ele: parse_xml(e) t = e.tail if t: yield t def main(): fp = "path/to/xml" tree = ET.parse(fp) root = tree.getroot() t_units = root.findall(".//{*}trans-unit") for source, target in t_units: for ele in parse_xml(source): print(ele)
运行后输出缺失了部分内容:
foo <Instruction> <grande>
但将函数中的yield替换为print后:
def parse_xml(ele: ET.Element): tag = ele.tag if not isinstance(tag, str) and tag is not None: return t = ele.text if t: print(t) for e in ele: parse_xml(e) t = e.tail if t: print(t)
能得到预期的完整输出:
foo <bar> <Instruction> <crow> <grande>
疑问:为什么使用yield时递归逻辑会被跳过?
原因分析与修复
问题核心在于生成器函数的特性:调用生成器函数只会返回一个生成器对象,不会自动执行函数内的逻辑,必须通过迭代(比如for循环、yield from)才能触发它产出值。
在带yield的初始版本中,递归调用parse_xml(e)时,只是创建了一个生成器对象但没有处理它,导致递归函数里的yield产出的<bar>、<crow>这些值完全没有被传递到上层迭代器,自然不会出现在输出里。而print版本中,函数调用会直接执行内部的打印逻辑,所以所有内容都能正常输出。
修复方案
只需修改递归调用的部分,让递归生成器的产出值被传递到上层:
方案1:使用yield from(Python 3.3及以上)
yield from可以直接迭代生成器并产出所有值,是处理递归生成器的简洁方式:
def parse_xml(ele: ET.Element): tag = ele.tag if not isinstance(tag, str) and tag is not None: return t = ele.text if t: yield t for e in ele: yield from parse_xml(e) # 传递递归生成器的所有产出值 t = e.tail if t: yield t
方案2:手动迭代递归生成器
如果使用Python 3.2及以下版本,可以手动循环迭代递归生成器的每个值并yield:
def parse_xml(ele: ET.Element): tag = ele.tag if not isinstance(tag, str) and tag is not None: return t = ele.text if t: yield t for e in ele: for item in parse_xml(e): # 手动迭代并产出递归生成器的每个值 yield item t = e.tail if t: yield t
修改后运行代码,就能得到和print版本一致的完整输出。
内容的提问来源于stack exchange,提问作者monopoly
相关产品推荐
相关产品推荐

