lxml是Python生态里解析和生成XML文档最常用的库之一,速度快、功能全,底层基于C语言实现的libxml2。不过不少人在用lxml构建完一棵XML树之后,调用tree.write保存文件,打开一看整个文档挤在一行里,标签之间没有任何换行和缩进,可读性几乎为零。这个问题其实只需要一个pretty_print参数就能解决,但实际使用中它会遇到不少坑,比如设置了参数却依然没有缩进、或者部分节点缩进错乱。下面就把这些情况逐一讲清楚。

一、pretty_print的基本用法
lxml提供了两种主要的序列化方式:一种是etree.tostring返回字节串或字符串,另一种是ElementTree.write直接写入文件。两者都支持pretty_print参数,设为True之后,lxml会在输出时自动在元素之间插入换行符,并按照层级添加两个空格的缩进。
from lxml import etree
root = etree.Element("library")
book = etree.SubElement(root, "book", id="1")
title = etree.SubElement(book, "title")
title.text = "Python编程"
tree = etree.ElementTree(root)
tree.write("library.xml", pretty_print=True, encoding="utf-8")执行后打开library.xml,可以看到每个元素独占一行,子元素比父元素多缩进两个空格,结构一目了然。如果只是想拿到格式化后的字符串而不是写文件,用etree.tostring(root, pretty_print=True).decode("utf-8")即可,效果完全一样。
需要提醒的是,pretty_print的缩进字符是固定的两个空格,lxml本身没有提供参数让你改成四个空格或者Tab。如果项目对缩进格式有严格要求,可以先用pretty_print输出,再用文本替换处理,或者用后面介绍的indent函数方案。
二、设置了pretty_print却没有缩进的原因
最常见的坑就是:明明传了pretty_print=True,文件还是挤成一团。这通常是因为XML树中已经存在手工设置的空白文本。lxml的格式化逻辑是这样的:只有当一个元素本身没有text内容、且它的子节点之间没有tail文本时,才会自动插入换行和缩进。一旦任何一个位置已经有空白字符,lxml就认为文档是刻意排版过的,会放弃自动格式化。
典型场景是从一个已经格式化过的XML文件解析再修改。解析时etree.parse默认会保留原文档中的换行和空格,这些内容被存到元素的text和tail属性里。之后即使你再保存时设置pretty_print=True,原有的空白还在,lxml不会二次格式化。解决办法是解析时加上参数去除无用空白:
parser = etree.XMLParser(remove_blank_text=True)
tree = etree.parse("input.xml", parser)
# 修改文档内容...
tree.write("output.xml", pretty_print=True, encoding="utf-8")remove_blank_text=True只删除那些只包含空白、且可以被判定为排版用的文本节点,不会影响真正有意义的空格内容,这是安全的标准做法。
另一个坑是子元素的tail属性。如果你在程序里给某个元素设置了elem.tail = "\n"之类的值,那么包含它的那一层就会失去自动缩进。处理原则很简单:打算依赖pretty_print,就完全不要手工添加任何空白节点,让lxml全权负责排版。
三、保存文件时的其他常用参数搭配
pretty_print很少单独使用,实际保存文件时通常还要配合几个参数。encoding指定输出编码,建议显式写明utf-8;xml_declaration控制是否在文件头输出XML声明,设为True时会生成类似<?xml version='1.0' encoding='utf-8'?>的一行;如果文档带DTD,还可以用doctype参数补充声明。
tree.write(
"output.xml",
pretty_print=True,
encoding="utf-8",
xml_declaration=True
)有一个细节要注意:当encoding传的是字符串(如"utf-8")时,write方法默认会写入XML声明,所以很多人没写xml_declaration也能看到声明行;而如果传的是unicode类型做输出,声明则默认不写。为了避免不同版本行为差异,建议显式声明,不要依赖默认值。
另外,带CDATA区的文档在格式化时要留意:lxml的pretty_print不会破坏CDATA内容本身,因为CDATA被当作普通text处理,但如果CDATA内容为空,序列化时可能变成自闭合或空标签形式,若下游系统对格式敏感,保存前最好验证一下输出结果。
四、用indent函数实现更灵活的缩进控制
如果不想引入lxml,标准库xml.etree.ElementTree从Python 3.9开始提供了ET.indent函数,可以在序列化前直接修改树的空白结构,而且支持自定义缩进字符:
import xml.etree.ElementTree as ET
tree = ET.ElementTree(root)
ET.indent(tree, space=" ") # 使用四个空格缩进
tree.write("output.xml", encoding="utf-8", xml_declaration=True)这种方式的思路和pretty_print不同:它是把缩进直接写入元素的text和tail,之后无论怎么序列化都保持格式,不依赖序列化参数。缺点是树结构被真实修改了,如果后续还要对这些文本做逻辑处理,需要先把空白清理掉。
两种方案怎么选?如果全程使用lxml且只是保存时需要好看一点,pretty_print=True最省事;如果需要自定义缩进宽度、或者代码要兼容不装lxml的环境,ET.indent更合适。两者不要混用,先indent再pretty_print反而可能出现双重空白。掌握这些要点后,保存出来的XML文件就能保持清晰规整的层次结构,无论是人工检查还是版本比对都会轻松很多。
Python lxmlpretty_printXML格式化修改时间:2026-09-14 00:56:48