☰
Python pprint 完全指南:结构化输出与调试
2026/10/13 1:27:24 网站建设 项目流程

如果你调试过那种层层嵌套的订单配置、爬虫抓回来的大字典、或者对接第三方API返回的复杂JSON,你一定经历过这种崩溃:一个print()下去,几千个字符全部挤在一行里,缩进没有、换行没有,键值关系全靠肉眼硬认,稍微深一点的层级就直接看花眼。

这个场景我太熟了。以前排查一个配置合并的问题,print()出来的结构长到终端自动换行,从第80列开始毫无规律地断行,我愣是对着一个屏幕找了十分钟才定位到是哪个键的值写错了。后来换成pprint,同样的数据,层级清清楚楚,键值对齐,问题一眼就暴露了。

pprint是 Python 标准库里被严重低估的一个模块。全称是 pretty printer,直译是“漂亮打印机”,但它干的活远不止“打印得好看”这么简单——它本质上是一个针对复杂数据结构的结构化输出引擎:递归地遍历对象、根据宽度和缩进阈值重新排版、把嵌套关系可视化。这篇东西我打算把这套工具从头到尾讲透,包含核心参数、四个常用接口、自定义对象扩展,以及我在实际项目里踩过的坑和总结出来的调试心得。不管你是在写脚本、做接口联调、维护配置系统,还是单纯想把日志打得清楚点,这篇文章应该都能让你少走不少弯路。

1. 先搞清楚pprint到底解决了什么问题

1.1print()的三个“原罪”

Python 内置的print()函数,遇到简单字符串是没问题的,但一旦碰上嵌套的数据结构,它的表现基本可以用“灾难”来形容。我总结了三个最典型的痛点:

第一,单行挤成一团。一个三层的字典,print()会把它渲染成一行极长的文本。终端宽度有限的,于是内容在第80列、第120列这些地方被硬生生截断,断点完全没有逻辑。你根本看不出来一个列表在哪里结束、一个字典从哪里开始。

第二,嵌套层级看不出归属。print()不会根据嵌套深度做缩进,即使你手动在字符串里拼\n和空格,也只是在展示层做文章,数据的真实结构并没有被还原出来。嵌套深度一超过两层,代码里的缩进和输出里的缩进就对不上了,读起来极其痛苦。

第三,键值顺序容易误导。Python 3.7 之后字典保持插入顺序,但如果你拿到的是从外部解析进来的数据(比如 JSON),print()输出时只会按照“数据原来的存储顺序”来展示,而这个顺序未必是逻辑上的顺序。你要是靠肉眼去比对字典内容,很容易被这种顺序迷惑。

pprint则是完全不同的思路:它不再把数据结构当成一个“字符串”来处理,而是把它当成一棵树来遍历,逐层展开,每层按照你设定的缩进规则排版。

1.2 “结构化输出”是什么意思

很多教程把pprint说成“美化打印”,这个描述其实掩盖了它的本质。它做的不是文字的修饰,而是对数据结构的重新表达。

它内部会递归地访问你传入的容器对象,判断每一个元素是字典、列表、元组还是集合,然后根据当前的缩进层级、剩余宽度、是否启用 compact 模式等因素,决定元素是继续放在同一行还是折行显示。这本质上是一个排版引擎,类似 IDE 里的“格式化代码”功能,只不过它针对的不是代码,而是任意 Python 对象。

把握住这个定位很重要,因为它解释了很多“奇怪”的输出行为。比如你设置width=40,一个很长的字符串在 40 列放不下时,pprint不会帮你把字符串从中间切断——因为它不能破坏字符串的完整性,只能让整个字符串独占一行甚至溢出。再比如depth参数,它控制的不是字符长度,而是递归深度,超过深度的部分直接显示为...。理解了“递归排版”这个本质,后面的参数和坑就都好解释了。

1.3 什么场景下最值得用它

结合实际经验,我认为这几类场景是pprint的主场:

  • 接口联调:后端返回的 JSON 结构复杂,用pprint.pprint(response.json())一打,字段层级一目了然,定位缺字段、类型不对的问题非常快。
  • 配置解析:读取 YAML、TOML 或者嵌套的 ini 配置后,直接pprint出来检查合并结果,比逐行打印日志高效得多。
  • 爬虫开发:抓下来的页面解析成字典或者对象数组后,结构化输出能让你快速判断哪些字段是空的、哪些节点被意外嵌套了。
  • 日志记录:用pformat()把格式化结果拼进日志消息,比print()更专业——这个后面会专门写。

一句话总结:只要你需要“观察”一个数据结构,而不是“传输”它,pprint就是比print()更合适的选择。

2. 五个核心参数,决定输出的三种“长相”

pprint的核心类是pprint.PrettyPrinter,而常用的pprint()函数本质上就是创建了一个默认参数的PrettyPrinter实例。可以这么说:理解了PrettyPrinter的构造函数,你就理解了整个模块。

pprint.PrettyPrinter(indent=1, width=80, depth=None, stream=None, *, compact=False, sort_dicts=True, underscore_numbers=False)

第一次看这个签名,大多数人只记住了indent和width。但实际上每个参数都会影响输出的形态,而且有些参数之间存在相互制约。我逐个拆开讲。

2.1 width 与 compact:折行逻辑的两把尺子

width默认值是 80,单位是“字符宽度”。它代表的是pprint允许的单行最大宽度。当一个容器的内容在当前行放不下时,pprint就会把它拆成多行展示。

看到这里你可能会想:那我把width设大一点,比如 200,是不是所有内容就会尽量挤在一行?对,确实如此,但这里有个微妙的联动关系:当compact=False(默认值)时,pprint倾向于“每个元素单独占一行”来保证清晰;当compact=True时,pprint在宽度允许的范围内会尽可能把多个元素放在同一行,以减少总行数。

我举一个实际对比的例子,数据是同一个:

import pprint data = { "name": "订单服务", "port": 8080, "tags": ["core", "payment", "high-availability"], "deps": ["auth", "user", "inventory"], }

默认参数打印出来是:

{'deps': ['auth', 'user', 'inventory'], 'name': '订单服务', 'port': 8080, 'tags': ['core', 'payment', 'high-availability']}

注意几个细节:最外层的每个键值对都在单独一行,因为默认不是 compact 模式;字典的键被排序了(这个sort_dicts后面讲);第一行{ 'deps'后面直接跟了值,是因为第一行还有足够空间,不需要换行。

如果设置compact=True:

pprint.pprint(data, compact=True)

输出会变成:

{'deps': ['auth', 'user', 'inventory'], 'name': '订单服务', 'port': 8080, 'tags': ['core', 'payment', 'high-availability']}

咦,好像没区别?关键在于,当compact=True时,列表内部的多个元素会尽可能挤在一行。上面这个数据里列表本来就因为width限制放不下所以折行了,可如果列表很短,区别就出来了:

short_data = {"items": [1, 2, 3], "name": "test"} pprint.pprint(short_data, width=40, compact=False) # 输出: # {'items': [1, # 2, # 3], # 'name': 'test'} pprint.pprint(short_data, width=40, compact=True) # 输出: # {'items': [1, 2, 3], # 'name': 'test'}

看到区别了:compact=False时,列表元素即使能放下也会一个一个自然折行;compact=True时,列表内部会选择“挤一挤”放在一行。所以我的经验是:如果视觉上想少翻几页,开compact=True;如果希望结构尽可能松散、方便逐行比对,保持默认 False。

2.2 indent:别小看这个起始缩进

indent默认值是 1,它的作用范围是当前容器开始换行后,新行相对于容器起始位置的额外缩进量。很多人把它理解成“每一层的缩进宽度”,这个说法不够准确,因为它只影响“额外缩进”,基础的层级缩进是由pprint自己管理的。

看个例子最直观:

nested = {"a": {"b": {"c": 1}}} pprint.pprint(nested, indent=4)

输出:

{ 'a': { 'b': { 'c': 1}}}

注意,{后面跟了 4 个空格,才开始写'a'。而内层'b'、'c'的缩进是在这个基础上的。这个参数对“层级感”的影响非常明显。如果你的数据嵌套程度很深,调大indent会让层级边界更清晰;但也要适可而止,太大会导致大量行宽被浪费,提前触发折行。

我在实际项目里通常保持默认indent=1,因为追求的是紧凑和可读性的平衡。只有在打印某些嵌套极深、内部结构相近的数据(比如 AST 树)时,才会把indent调到 2 或 3。

2.3 depth:防止“深水炸弹”

depth应该是最被低估的参数。它的作用是限制递归展开的深度。默认是None,表示无限制;但当你面对一个层级非常深、甚至可能包含循环引用的数据结构时,无脑全量展开不仅输出爆炸,还可能导致程序性能问题。

deep = {"a": {"b": {"c": {"d": {"e": 1}}}}} pprint.pprint(deep, depth=2)

输出:

{'a': {'b': {...}}}

超过depth的部分会被替换成...,不会继续递归展开。这在处理高维数组、嵌套类对象、或者防御性地打印不可控的外部数据时特别有用。我自己写调试工具函数时,往往会加一个max_depth参数,默认给 3,防止某些沙雕数据把日志直接刷爆。

另外,depth还有一个副作用:它能在一定程度上保护你免受“自引用”结构的无限递归影响。比如一个列表把自己 append 了进去,depth可以帮助截断输出。

2.4 sort_dicts:排序开关,暗藏一个坑

从 Python 3.8 开始,pprint新增了sort_dicts参数,默认True。也就是说,默认情况下,pprint输出的字典键会被排序,而不是按照插入顺序。

这个默认行为对调试来说经常是好事:你可以快速比较两个字典的结构差异,不用管它们内部键的顺序有多不一致。但也有人会困惑:“为什么我定义的顺序到了pprint这里就不一样了?”原因就是sort_dicts。

如果你希望保持字典的原始插入顺序,必须显式设置sort_dicts=False。这里藏着一个小坑:pprint.pprint()作为函数调用时,sort_dicts需要通过关键字参数来传;如果你习惯了位置传参,很容易把stream的位置挤掉,比如写成pprint(data, False)——这会把False当作stream参数,然后直接报错。

# 正确写法 pprint.pprint(order_data, sort_dicts=False) # 错误写法,False 被当成 stream,TypeError pprint.pprint(order_data, False)

当你在处理有序字典(比如OrderedDict)时,我会建议主动设置sort_dicts=False,否则它里面的语义顺序会被“按键排序”破坏,影响你对状态的判断。

3. 接口四件套:pprint、pformat、pp与saferepr

使用pprint模块时,你会发现它有多个看起来差不多的接口。我刚开始用的时候也搞混过,后来才总结清楚:这其实是四种不同用途的“打开方式”。

3.1pprint():最常见的快速打印

pprint.pprint(obj, stream=None, indent=1, width=80, depth=None, *, compact=False, sort_dicts=True)

它的特征是:直接向sys.stdout(或者你指定的stream)输出格式化结果,没有返回值。这是日常调试最顺手的一个。你随便在交互式环境里pprint.pprint(data),结果直接打到屏幕上。

有一个值得注意的细节:pprint()在输出结束后不会添加额外的空行,但如果你的数据以换行结尾,它可能会保留。

3.2pformat():把格式化结果“握在手里”

s = pprint.pformat(obj, indent=1, width=80, depth=None, *, compact=False, sort_dicts=True)

pformat()和pprint()的区别只有一个:它返回格式化后的字符串,而不是直接打印。这个差别在实战中太重要了。

我来讲一个场景:你现在在写一个复杂的服务,需要把某一次请求的入参数组打印到日志里。如果用pprint(),它会把内容直接打到控制台,这时候你想往日志文件里写一份、想加上时间戳、想放到异常信息里,就全做不了了。但如果用pformat():

import pprint import logging logger = logging.getLogger("order") request_data = {"user_id": 12345, "items": [{"sku": "A100", "qty": 2}, {"sku": "B200", "qty": 1}]} logger.debug("request payload: %s", pprint.pformat(request_data))

这样日志里既有完整的结构层次,又不会受print()的缓冲策略影响。可以说,凡是需要“把格式化结果当字符串用”的场景,都应该选择pformat()。

3.3pp():Python 3.12 加入的快捷方式

Python 3.12 里新增了一个高层函数pprint.pp()。它的签名是:

pprint.pp(object, *args, sort_dicts=False, **kwargs)

和pprint.pprint()的主要区别在于sort_dicts默认值被改成了False。这是社区反馈后的一个修正,因为很多人在用pprint的时候,本身就想保留字典原本的顺序,结果每次都要手动传sort_dicts=False,非常烦人。

所以如果你用的是 Python 3.12+,而且你想要保留原始顺序,直接用pp()就行:

pprint.pp(my_dict)

这个函数在功能上等价于pprint(..., sort_dicts=False),但更简洁。如果你还在用较老的 Python,那只能自己手动传参数了。

3.4saferepr()与子类化:高级玩家的扩展点

pprint.saferepr()是一个相对少用的函数,它生成一个“安全”的 repr 字符串,保证不会因为对象存在递归引用而抛RecursionError。当你处理可能包含自引用的对象时,直接调用默认repr()有风险,而saferepr()会把递归部分处理成...。

更强大的扩展方式是子类化PrettyPrinter并重写format()方法。举个例子,假设你有一个自定义的配置对象,默认打印出来全是内存地址,你想让它在pprint里展现出内部字段,这时候你可以这样做:

import pprint from dataclasses import dataclass, asdict from decimal import Decimal @dataclass class PriceItem: sku: str price: Decimal qty: int class PrettyConfigPrinter(pprint.PrettyPrinter): def format(self, object, context, maxlevels, level): if isinstance(object, PriceItem): # 返回三元组:文本表示、是否可读、是否递归 return (f"PriceItem(sku={object.sku!r}, price={object.price}, qty={object.qty})", True, False) if isinstance(object, Decimal): return (f"Decimal('{object}')", True, False) return super().format(object, context, maxlevels, level)

这会让你打印包含PriceItem和Decimal的复杂结构时,不再是一堆<xxx object at 0x...>或者裸数字,而是可读性极高的字段级展示。这算是我实际工作中一个非常有用的定制技巧。

4. 实战场景一:API 响应与嵌套配置的排查

理论讲得再多,不实战都是空的。这一节我用实际工作中遇到过的场景来演示pprint怎么用。

4.1 模拟一个真实的三层嵌套 JSON

假设你正在对接一个支付回调接口,返回的 JSON 长这样(我简化了,但保留了多层嵌套):

callback_payload = { "event": "payment.success", "order_id": "20240815001", "amount": 168.50, "currency": "CNY", "customer": { "user_id": "U12345", "vip_level": 3, "contacts": [ {"type": "mobile", "value": "138****8000", "primary": True}, {"type": "email", "value": "u12345@example.com", "primary": False}, ], }, "items": [ {"sku": "A100", "name": "无线耳机", "price": 99.0, "tags": ["digital", "hot"]}, {"sku": "B200", "name": "充电器", "price": 69.5, "tags": ["digital", "accessory"]}, ], }

现在你要排查一个问题:这个回调里的items[0].tags到底是不是两个元素?如果用默认print(),输出的长字符串会让人崩溃。但用pprint.pprint():

import pprint pprint.pprint(callback_payload, width=60)

输出会变成:

{'amount': 168.5, 'currency': 'CNY', 'customer': {'contacts': [{'primary': True, 'type': 'mobile', 'value': '138****8000'}, {'primary': False, 'type': 'email', 'value': 'u12345@example.com'}], 'user_id': 'U12345', 'vip_level': 3}, 'event': 'payment.success', 'items': [{'name': '无线耳机', 'price': 99.0, 'sku': 'A100', 'tags': ['digital', 'hot']}, {'name': '充电器', 'price': 69.5, 'sku': 'B200', 'tags': ['digital', 'accessory']}], 'order_id': '20240815001'}

这才是“结构化输出”应有的样子。你能看到:

  • 顶层键按字母排序(amount、currency、customer...),这里用了默认的sort_dicts=True。在接口返回的场景,我会更关心“数据里有没有”而不是“顺序如何”,所以默认排序能让我快速定位键。
  • 每一层的缩进都非常明确,contacts下面的字典嵌套关系一眼可见。
  • 由于我设置了width=60,深层嵌套的行在放不下时自然折行,而不是硬挤出屏幕。

4.2 对width折行的几个常见误解

很多人一看到width=60就以为“所有行都必须 60 列以内”,这是不对的。pprint不会强行切断单个不可分割的元素。比如上面'value': '138****8000'如果特别长,超出了 60,它不会把字符串从中间断开,而是仍然整行输出,顶多导致这一行超过宽度限制。

这个逻辑类似 Web 排版里的word-wrap和overflow-wrap:容器宽度只是参考,遇到长单词(长字符串、长数字)就允许撑破边界。理解这一点,你看到“为什么我的 width 设置了还是超出”时就不会疑惑了。

4.3 处理集合、冻结集合等无序类型

pprint打印set的时候也有自己的一套逻辑。集合是无序的,但pprint会尽量保证输出的确定性。比如:

pprint.pprint({"apple", "banana", "orange"}, width=20)

它可能会把集合元素拆成多行,每行几个,而且多次打印顺序可能不同,因为集合本身没有顺序。这里我不建议你把pprint的输出结果用于任何“对集合内容做断言”的自动化场景,它只适合肉眼观察。

如果需要稳定输出,可以先转成sorted(set),不过要注意元素类型必须可比较,否则会TypeError。这种场景其实比较少见,提醒一下就好。

5. 实战场景二:自定义对象与 dataclass 的输出优化

默认情况下,pprint遇到一个不是内置容器类型的对象时,它调用的是对象的__repr__()。如果这个对象没有实现好__repr__,输出就是<module.ClassName object at 0x...>,这基本等于没输出。这个坑在 Debug 业务对象时非常常见。

5.1 先看一个反面教材

假设你定义了这样的类:

class Order: def __init__(self, order_id, amount, items): self.order_id = order_id self.amount = amount self.items = items order = Order("20240815001", 168.5, [{"sku": "A100", "qty": 1}]) pprint.pprint(order)

输出是类似这样的一行:

<__main__.Order object at 0x7f8a1c1b3a90>

你什么都看不到。对象里字段的值、列表的内容,全被遮住了。很多人到这里就开始抱怨“pprint没用”,其实不是pprint的问题,而是这个类没有提供可被利用的表示形式。

5.2 用自己的__repr__修复

最简单的解决方案,是在自定义类里实现__repr__方法。我不想引战,但__repr__的核心目的就是“给开发者看”,所以把它做得可读是有价值的。

class Order: def __init__(self, order_id, amount, items): self.order_id = order_id self.amount = amount self.items = items def __repr__(self): return (f"Order(order_id={self.order_id!r}, amount={self.amount!r}, " f"items={self.items!r})")

这样pprint.pprint(order)就会把你看到的Order对象变成一个可读的字符串行,夹在复杂容器里也能看清楚了。

不过要注意:pprint对自定义对象的进一步嵌套展示仍然有限。它不会帮你把Order.items里的列表信息单独再拆一层排版,它只会把这个字符串当作一整段文本输出。如果你希望自定义对象也享受“容器级”的逐层结构输出,那就需要回到第 3.4 节里提到的方式——子类化PrettyPrinter,重写format()方法。

5.3 用format()方法实现字段级展开

用format()控制输出的好处是:你可以把对象的内部字段“伪装”成pprint能理解的容器结构,让它继续递归处理。例如:

class OrderPrinter(pprint.PrettyPrinter): def format(self, object, context, maxlevels, level): if isinstance(object, Order): # 手动拼一个字典,让 pprint 当作普通嵌套结构继续递归排版 return ({ "order_id": object.order_id, "amount": object.amount, "items": object.items, }, True, False) return super().format(object, context, maxlevels, level)

不对,这里需要注意:format()返回的元组中,第一个元素必须是一个字符串,而不是容器对象。上面我差点写错了。正确的做法是生成一个“格式良好的字符串”,并把is_readable字段设为True。我实际用的版本是这样的:

class OrderPrinter(pprint.PrettyPrinter): def format(self, object, context, maxlevels, level): if isinstance(object, Order): text = pprint.pformat( { "order_id": object.order_id, "amount": object.amount, "items": object.items, }, indent=self._indent_per_level, width=self._width, depth=maxlevels, compact=self._compact, ) return (text, True, False) return super().format(object, context, maxlevels, level)

这个方案的精髓在于:你在自定义对象的format()里调用pformat()再给它一次“重新排版”的机会,生成的字符串本身就是格式化好的多行文本,然后外层PrettyPrinter会把它原样吸收进去。输出效果就很接近原生容器了。

不过老实说,这个技巧更偏“救火”,用起来也有点绕。大多数情况下,我更建议大家直接给业务对象写一个清晰的__repr__,然后在打印时用dataclasses.asdict()或者vars()把它们转成字典再打印,这样更简单也更稳妥。

5.4 与logging模块的结合

一个很自然的延伸:如果你在日志里记录结构化数据,用pformat()几乎是最好的选择。比如:

logger.info("order created, detail:\n%s", pprint.pformat(order_ctx, width=120, sort_dicts=False))

width=120是因为日志文件一般可以接受比终端更宽的行;sort_dicts=False是因为你希望日志里保留上下文原本的字段顺序,方便对照代码。在这个场景里稍微注意一下:如果日志级别是INFO而你的数据结构很大,每次即便不输出也会执行pformat(),影响性能。最好判断一下logger.isEnabledFor(logging.DEBUG):

if logger.isEnabledFor(logging.DEBUG): logger.debug("request: %s", pprint.pformat(payload, depth=5))

这个小优化在处理超大请求体时能省不少时间。

6. 它和json.dumps到底谁更香

在讨论 Python 结构化输出时,总有人拿json.dumps(indent=2)和pprint做对比。这两个工具确实有一部分重叠,但核心定位差别很大。

6.1 两者核心差异

维度pprintjson.dumps
数据格式任意 Python 对象(含 set、Decimal、dataclass、自定义对象)必须是 JSON 可序列化的类型
输出目的给人看的调试/展示数据交换、存储、序列化
键顺序默认按排序,可关闭保持字典插入顺序
中文处理直接输出默认ensure_ascii=True,中文转义成\uXXXX
递归处理支持深度截断、自引用保护自引用会抛异常
循环引用可处理(有限深度)不支持,直接ValueError

说人话就是:json.dumps的第一目标是“把对象变成 JSON 字符串”,而不是“把对象变得好看”。只要数据里有Decimal、有datetime、有自定义对象,json.dumps就直接罢工或者要你写一堆default处理函数。而pprint对这些类型宽容得多——它不关心你能不能序列化,只关心你能不能看清。

6.2 中文和特殊字符的差距

我实打实遇到过这个问题。某次联调,我用json.dumps(data, ensure_ascii=False, indent=2)打印一个包含中文的配置,倒是正常显示中文了,但是输出里引号都是双引号,键值对之间还有空格,看起来像 JSON 格式,但又不完全是可读性最好的展示。

而pprint遇到中文就直接原样输出,不需要任何额外参数。举个例子:

data = {"名称": "订单服务", "描述": "处理订单创建与支付回调"} pprint.pprint(data) # 输出:{'名称': '订单服务', '描述': '处理订单创建与支付回调'}

如果你的项目需要做数据交换、存文件、给前端,选json.dumps没错;但如果只是为了让自己快速看清一个结构,选pprint更省事。

6.3 一个综合建议

我自己的习惯是:要落盘、要传输,用json.dumps;要观察、要调试、要打日志,用pprint。尤其是面对嵌套很深的字典时,pprint的递归排版能力比json.dumps(indent=2)更加直观,因为它能结合depth、compact等参数按需控制展开程度,而json.dumps只能全量展开或者容忍巨大的输出。

如果你非要用json.dumps调试复杂对象,也不是不行,配合一个default=str的方法可以把Decimal、datetime转成字符串,但这样会丢失这些对象的原始类型信息,打印出来就不太准确了。相比之下pprint对类型的保真度更高。

7. 常见问题与排查技巧实录

最后,把我在实际使用中遇到的、以及在给同事答疑时被反复问到的问题,整理成一份速查清单。

7.1 为什么输出的字典键顺序总是“乱”的

很多人第一次用pprint会困惑:“我定义的时候明明是 A、B、C,输出怎么变成了 C、A、B?”原因就是sort_dicts=True默认开启。这个设计初衷是让结构化内容的比较变得更容易。如果你想保持插入顺序,设置sort_dicts=False或者直接用 Python 3.12 的pp()即可。

7.2 为什么设置了width=40,行还是超出了

这个我在前面讲过:pprint不会把一个不可分割的元素(长字符串、长数字)强行折断。它宁愿让这一行超出width,也不会把元素的内部结构打乱。这种现象在打印长文本、URL、Base64 编码时特别常见,不用惊讶,这是设计使然。

7.3 为什么depth=2还是输出了很多内容

要理解depth的粒度。它数的不是“层数”而是“递归层级”。容器内的元素属于第 1 层,如果元素本身是容器,它的内部字段属于第 2 层。超过depth的层会显示...。所以depth=2对于一个 10 层的嵌套来说,会在很浅的地方就截断,输出自然就简练了。如果你发现输出还是很多,那可能是因为顶层元素太多——depth限制的是深度,不是广度。

7.4 为什么自定义对象打印出来是一堆<... object at 0x...>

因为pprint在没有更多信息时,只能回退到__repr__()。解决方案有三种:在类里实现可读的__repr__;用dataclasses.asdict()转换成字典后再打印;或者子类化PrettyPrinter重写format()。按成本排序,我一般先试第一种,不行就第二种,真的需要保留复杂嵌套结构时再上第三种。

7.5 打印出来的数据太大、太慢怎么办

如果你的对象是一棵几百层深的树,或者一个包含数万个元素的大列表,直接全量pprint可能会卡顿甚至刷屏。这时候优先设置depth,限制递归深度;其次设置width较小值,让折行更早发生,也能变相减少单行长度。如果还嫌慢,可以考虑只打印关键层级,比如先打印顶层键,然后手工提取你关心的分支再打印。我用过的一个技巧是写一个递归裁剪函数,把不需要的分支塞成...,再交给pprint输出,效果很好。

7.6 写文件时中文乱码

如果你用stream=open("out.txt", "w")输出,记得指定编码为utf-8,否则在 Windows 默认编码下中文会乱码。更推荐的做法是直接用pformat()得到字符串,然后自己用open("out.txt", "w", encoding="utf-8")写入,对编码控制更清晰。还有一个小习惯:如果你是在日志文件里看结构化内容,日志系统通常已经配置了编码,你只需要关注pformat()产生的内容即可。


实际用下来,我的整体感受是:pprint属于那种“用起来简单、用好了不简单”的工具。它的核心逻辑并不复杂,核心就是递归 + 宽度控制 + 缩进管理,但真正理解那几个参数如何配合,才能在各种复杂数据结构面前游刃有余。这里我分享一个自己长期使用的习惯:在项目的调试工具模块里,封装一个dump()函数,固定参数为width=100, sort_dicts=False, depth=6,然后内部用pformat()接上logger。这样既避免了重复写参数,又保证了所有结构化输出的风格统一,遇到需要快速确认数据结构的场合,一行代码就能搞出清爽的层次来。这个思路你们也可以试试看。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询