iter()遍历当前元素的所有后代节点(深度优先),非仅直接子节点;想查直接子节点应使用list(elem)或for child in elem:。

iter() 为什么找不到深层嵌套的子节点
因为 iter() 默认只遍历当前元素及其**所有后代节点**(depth-first),不是“仅直接子节点”——这点和 getchildren()(已弃用)或 list(elem) 完全不同。很多人误以为它像 for child in elem: 那样只扫一层,结果漏掉深层结构。
常见错误现象:elem.iter('tag') 返回空,但手动 inspect 发现目标 tag 其实藏在三层子节点下面;或者遍历时意外匹配到本不该出现的远亲节点。
- 用
elem.iter()就是查整棵子树,不设限;想只查直接子节点,改用list(elem)或for child in elem: - 指定标签名时,
elem.iter('div')只返回所有名为div的后代,不含其他标签;不传参数则返回所有节点(含文本、注释等) - 注意命名空间:如果 XML 带 namespace(如
{http://www.w3.org/1999/xhtml}div),必须用完整带 ns 的字符串匹配,或提前用register_namespace配合通配前缀
iter() 和 findall() / find() 的关键区别在哪
iter() 是迭代器,不建新列表,内存友好;findall() 返回 list,立即求值。但更重要的是语义差异:前者是“从当前节点往下无条件扫全部”,后者是“在直接子节点中按 XPath 查”。哪怕写 elem.findall('.//tag'),也仍是先取子节点再对每个做 XPath 求值,性能和行为都不同于 elem.iter('tag')。
-
elem.iter('tag')→ 单次 DFS 遍历,快,适合“只要找到所有 tag,不管在哪层” -
elem.findall('.//tag')→ 对每个直接子节点调用 XPath 引擎,有额外开销,且某些老版本 ElementTree 不支持.//写法(会报SyntaxError: cannot use absolute path on element) - 如果只要最深一层的某个 tag(比如最后一个
item),别用list(elem.iter('item'))[-1]—— 改用elem.iterfind('.//item').__next__()配合reversed()或手动循环,避免全量加载
遇到空白文本节点或换行符怎么过滤
XML 解析后,换行、缩进、空格都会变成 text 或 tail 属性为字符串(可能只含 '\n ')的 Element 节点。而 iter() 默认会把这些也当节点返回,导致后续逻辑出错,比如 elem.iter() 数出来 12 个节点,实际有意义的只有 5 个。
立即学习“Python免费学习笔记(深入)”;
- 判断是否为纯空白节点:
node.text and node.text.strip()或node.tag is not None(因为文本节点tag == None) - 安全跳过:
for node in elem.iter(): if node.tag is None: continue—— 这能滤掉所有文本/注释节点 - 更稳妥的做法是在解析时就压缩空白:
ET.XMLParser(remove_blank_text=True),再传给ET.parse()或ET.fromstring()
Python 3.9+ 中 iter() 的兼容性坑
3.9 开始,iter() 的返回类型从 generator 改为 Iterator[Element],表面没区别,但如果你写了类似 nodes = list(elem.iter()); nodes[0].clear(),然后又想继续用 nodes,会发现清空后原节点已变——这不是 bug,是 iterator 的自然行为。更隐蔽的问题是:某些旧工具链(比如用 isinstance(..., collections.Iterator) 做判断)可能误判。
- 不要反复消费同一个
iter()结果:它是一次性的,第二次list(elem.iter())才是新遍历 - 避免对
iter()结果做索引或切片操作;真要随机访问,先转list() - 跨版本安全写法:
[n for n in elem.iter() if n.tag]比filter(lambda x: x.tag, elem.iter())更直观,也避开生成器嵌套陷阱
真正麻烦的不是语法,是 XML 里那些没声明 namespace 却偷偷用了前缀的文档,还有混着 CDATA 和实体引用的 text 内容——这些地方 iter() 照样返回节点,但你得自己判断 node.text 是原始字符串还是已被解析过的值。










