Python列表打印进阶:从print到pprint与json的实战指南
2026/9/4 23:28:17 网站建设 项目流程

你在调试 Python 程序时,是不是经常直接写一句print(my_list)来看数据?数据量小的时候还能忍,一旦列表里有几十个元素、嵌套几层结构,输出就会挤成密密麻麻的一行。中文乱码、换行丢失、类型显示不清楚、日志里内容被截断,这些问题几乎每个人都遇到过。

列表打印看似只是print的基础用法,但在真实项目里,它牵扯到三个层次的问题:第一层是“能不能把整个列表显示出来”,第二层是“能不能按可读的格式显示出来”,第三层是“显示结果是否便于日志分析、接口排错和数据核对”。很多时候线上问题排查效率低,不是算法复杂,而是最基础的数据打印方式没选对。

这篇文章会从 Python 中列表打印的基础写法讲起,逐步深入到格式化输出、嵌套结构处理、日志场景下的打印策略,并给出一套在实际开发中可以直接复制使用的做法。读完你会有一个明确判断:什么时候直接print(list)就够了,什么时候必须换成循环、join()pprint或 JSON 序列化。

1. 这篇文章真正要解决的问题

先看一个很常见的场景。你从数据库查出 10 条用户记录,每条记录是一个字典,放进一个列表里,然后直接print(users)。控制台输出的结果可能是这样的:

[{'name': '张三', 'age': 28, 'city': '北京'}, {'name': '李四', 'age': 32, 'city': '上海'}, {'name': '王五', 'age': 24, 'city': '广州'}]

这条输出有几个问题。

第一,阅读困难。所有记录挤在同一行,字段和记录之间的边界不清晰。如果列表里有 100 条记录,控制台会横向滚动或自动换行,你很难快速找到“李四”的数据。

第二,中文显示可能异常。在某些终端或日志系统中,直接输出中文会变成'\u5f20\u4e09'这种转义序列,看起来像乱码,实际上是因为默认的repr()策略把非 ASCII 字符转义了。

第三,深层嵌套结构完全不可读。如果列表里的元素是字典,字典里又有列表,列表里又有字典,那么直接print()输出的结果基本只能用来“确认程序没报错”,无法用来核对逻辑。

第四,日志场景下信息不完整。使用logging模块时,如果直接把大列表塞进日志消息,日志系统会按整条消息处理。你无法控制缩进、无法分条记录,后期用日志平台检索时也只能搜到一整块字符串。

这篇文章要解决的问题,就是围绕“Python 列表如何优雅、清晰、可控地输出”,梳理出适合不同场景的打印方式。它不是一个复杂的算法问题,但它直接影响开发调试效率、日志可读性和线上问题定位速度。凡是写过 Python 脚本处理数据、写接口返回列表、做数据处理任务的人,都值得把这篇内容完整过一遍。

2. 基础概念:print、列表与可迭代对象

在展开写法之前,先把三个基本概念说清楚。很多初学者混淆它们,导致选择错误的打印方式。

2.1 print 到底做了什么

print()是 Python 的内置函数。它的核心行为是把传入对象的“字符串表示形式”输出到标准输出流。这里有两个关键点:

第一,print()不负责“转换数据格式”,它只是调用对象的__str__()__repr__()方法,拿到字符串,然后写到标准输出。

第二,多个参数传入时,print()默认用空格分隔,并在末尾追加换行符。这就是为什么print(item)在循环里会一行一个元素。

看一个简单示例:

user = {"name": "Alice", "age": 30} print(user) # 实际调用的是 dict 的 __repr__() print(str(user)) # 显式转成字符串,效果通常一样 print(repr(user)) # repr 与 str 在很多内置类型上结果相同

对于列表、字典这类容器类型,str()repr()返回结果基本一致,都是通过元素的repr()组合出来的。这意味着只要列表里的元素有自定义类,打印效果就受该类的__repr__()方法影响。

2.2 列表是容器,不是普通字符串

列表list是 Python 中最常用的可变序列类型。它可以存放任意类型的数据:数字、字符串、字典、对象,甚至可以嵌套列表。

正因为它是“容器”,直接打印容器时,Python 需要把容器内部每个元素的字符串表示拼接出来。这个过程是递归的,所以嵌套结构会变成一大串文本。容器打印的重点不是“输出”这个动作,而是“如何控制容器内部元素的展示规则”。

2.3 可迭代对象不等于列表

在写打印逻辑时,还会遇到一个容易混淆的概念:可迭代对象。

nums_range = range(5) nums_list = [0, 1, 2, 3, 4]

直接print(nums_range)会输出range(0, 5),而不是元素内容。类似的情况还有生成器、迭代器、字典的键视图等。它们可以被for循环遍历,但不一定实现了友好的__repr__()方法。处理它们时,需要先用list()转换,或者用循环逐个打印。

下面的表格总结了不同容器的直接打印效果:

数据类型示例print()输出示例说明
列表[1, 2, 3][1, 2, 3]直接输出方括号形式
元组(1, 2, 3)(1, 2, 3)显示圆括号
字典列表[{"a": 1}][{'a': 1}]单行,内容多时难读
rangerange(3)range(0, 3)不会展开成元素
生成器(x for x in range(3))<generator object ...>不消耗则看不到元素
集合{1, 2, 3}{1, 2, 3}无序,每次输出顺序可能不同

理解这些区别后,你会发现“打印列表”不是在写一个简单语法,而是在决定数据的展示形式和排查成本。

3. 环境准备:你需要什么

本文的示例代码全部使用 Python 3 编写。实际开发中,Python 3.8 以上版本都可以直接运行,不需要额外安装第三方库。

如果你的环境还没有准备好,建议先检查版本:

python --version

Windows 下可能是py --versionpython3 --version。只要输出是 Python 3.x 即可。

部分示例会用到pprintjson,它们都是 Python 标准库模块,不需要 pip 安装。脚本保存为.py文件后,在终端运行:

python demo_print_list.py

如果你是初学者,推荐用 IDE 或 VS Code 运行,方便观察输出。如果只是在交互式环境里测试,直接复制代码块即可。为了统一,下文示例默认你在项目目录下创建脚本文件并运行。

4. 基础打印方式拆解:从最简单到最常用

这一节我们从最简单的写法开始,逐层深入。每种方式我都会说明它适合什么场景,不适合什么场景。

4.1 直接打印整个列表

直接打印列表是 Python 学习过程中接触到的第一种方式:

# 文件路径:demo/basic_direct.py fruits = ["apple", "banana", "cherry"] print(fruits)

运行结果:

['apple', 'banana', 'cherry']

这种方式的优势是代码短、意图明确。调试时你先看一眼元素是否存在,用它最方便。

但它有几个明显局限:

一是列表元素数量大时,所有内容会挤在同一行。在终端里,超过宽度后显示会换行,换行位置不可控,阅读体验差。

二是如果元素中包含自定义对象,直接打印可能显示为<__main__.Person object at 0x...>,完全没有业务信息。

三是不利于日志检索。每一行日志是一个完整事件,把几百个元素塞进一行会让日志平台难以对单个元素建立索引。

所以,直接print(list)只适合元素少、结构简单、纯调试的场景。

4.2 使用 for 循环逐行打印

当列表元素需要逐行展示时,for循环是最直白的方案:

# 文件路径:demo/basic_for_loop.py fruits = ["apple", "banana", "cherry"] for fruit in fruits: print(fruit)

运行结果:

apple banana cherry

这个方式适合日志记录,因为每行输出对应一个元素。你可以在循环内继续扩展,例如加编号、加前缀、加判断条件。

给元素加编号是常用需求,可以用enumerate()

# 文件路径:demo/basic_enumerate.py fruits = ["apple", "banana", "cherry"] for index, fruit in enumerate(fruits, start=1): print(f"{index}. {fruit}")

运行结果:

1. apple 2. banana 3. cherry

for 循环是处理列表打印时最可靠的基础设施。它的缺点是代码相对啰嗦,如果只是要把元素用逗号拼成一行,它就不是最优选择。

4.3 使用 join() 拼接字符串列表

如果列表里的元素全部是字符串,并且你希望它们输出在同一行、用指定分隔符隔开,join()是最优雅的方式:

# 文件路径:demo/basic_join.py tags = ["python", "list", "print"] line = ", ".join(tags) print(line)

运行结果:

python, list, print

join()的原理是以调用它的字符串作为分隔符,把传入的字符串可迭代对象拼接成一个新字符串。注意,这里要求列表元素必须都是字符串。如果混入整数,需要先转换:

# 文件路径:demo/basic_join_mixed.py numbers = [1, 2, 3] line = ", ".join(str(num) for num in numbers) print(line)

运行结果:

1, 2, 3

这种写法的核心价值在于:你完全控制分隔符、没有多余的方括号、没有字符串引号,非常适合生成 CSV 一行、日志中的关键字段拼接、对外输出友好提示等场景。

4.4 通过解包传递多个参数

print()本身支持多个位置参数。你可以用解包操作符*把列表元素作为多个参数传入,实现空格分隔的输出:

# 文件路径:demo/basic_unpack.py nums = [1, 2, 3] print(*nums)

运行结果:

1 2 3

这种方式适合快速查看一组数字。你还可以自定义sep参数:

print(*nums, sep="-")

运行结果:

1-2-3

但这种写法有一个需要注意的坑:如果列表有几千个元素,*nums会展开成几千个位置参数,虽然 Python 能接受,但可读性和极端情况下的参数数量限制都需要考虑。这种写法更适合少量元素。

4.5 小结:基础方式如何选

根据上面几种方式,可以整理出简单的选择原则:

需求推荐方式理由
快速确认列表内容直接print(list)代码最少
每行一条数据for循环天然分行
需要编号输出enumerate()同时拿到索引
元素是字符串,需要拼接join()分隔符可完全控制
快速输出一组数字print(*list)简洁直观

对于真实开发,光会这些还不够。因为项目里最常见的列表不是基础类型列表,而是字典列表,下面单独展开。

5. 复杂场景打印:字典列表与嵌套结构

5.1 直接打印字典列表的痛点

假设接口返回的数据是列表,列表中的每一项是一个用户字典:

# 文件路径:demo/dict_list_direct.py users = [ {"name": "张三", "age": 28, "city": "北京"}, {"name": "李四", "age": 32, "city": "上海"}, {"name": "王五", "age": 24, "city": "广州"}, ] print(users)

输出是单行的,上面我们已经看到了效果。它对人类阅读非常不友好,尤其是字段多、记录多时。

5.2 使用 pprint 美化输出

Python 标准库中的pprint模块专为“美化打印数据结构”设计。它能够根据数据结构的嵌套深度自动缩进,让字典列表变得层次分明:

# 文件路径:demo/dict_list_pprint.py from pprint import pprint users = [ {"name": "张三", "age": 28, "city": "北京"}, {"name": "李四", "age": 32, "city": "上海"}, {"name": "王五", "age": 24, "city": "广州"}, ] pprint(users)

输出:

[{'name': '张三', 'age': 28, 'city': '北京'}, {'name': '李四', 'age': 32, 'city': '上海'}, {'name': '王五', 'age': 24, 'city': '广州'}]

pprint会在容器内尽可能保持可读性,宽度超过默认阈值时主动换行。你还可以通过参数控制输出宽度和缩进:

from pprint import pprint nested = [ {"name": "张三", "tags": ["vip", "developer"], "address": {"city": "北京", "district": "海淀"}}, {"name": "李四", "tags": ["admin"], "address": {"city": "上海", "district": "浦东"}}, ] pprint(nested, width=60, sort_dicts=False)

sort_dicts=False表示保持字典原有的键顺序。Python 3.8 以上支持此参数。width=60让嵌套结构更容易被控制在可读宽度内。

pprint适合开发调试验证阶段。它的输出目标是人眼,不适合直接存入日志文件,因为多行文本在日志系统中会被拆成多条事件。

5.3 使用 json.dumps 输出规范格式

当数据结构完全由 JSON 支持的字段构成(字符串、数字、布尔值、字典、列表、None),使用json.dumps()是更好的选择。它有两个优势:格式标准、可以通过缩进让嵌套结构变得非常清晰。

# 文件路径:demo/dict_list_json.py import json users = [ {"name": "张三", "age": 28, "city": "北京"}, {"name": "李四", "age": 32, "city": "上海"}, ] print(json.dumps(users, ensure_ascii=False, indent=2))

输出:

[ { "name": "张三", "age": 28, "city": "北京" }, { "name": "李四", "age": 32, "city": "上海" } ]

这里有两个关键参数必须说明。

ensure_ascii=False非常关键。如果不设置它,JSON 模块默认会把所有非 ASCII 字符转成\uXXXX,导致中文变成转义序列:

[ { "name": "\u5f20\u4e09", "age": 28, "city": "\u5317\u4eac" } ]

这不是 bug,而是 JSON 规范允许的表现形式。但在人的阅读视角里,这种输出会给调试带来额外心智负担。所以处理含中文数据时,建议设置ensure_ascii=False

indent=2indent=4控制缩进。设置为None时输出紧凑模式,适合日志单行存储;设置为数字时输出美化模式,适合人眼阅读。

json.dumps还经常用于数据“回显”,比如接口联调时把请求参数或响应体打印到控制台,服务端和客户端之间的数据结构是否一致,一眼便能判断。

5.4 使用 pprint 和 json 的边界

有人会问:pprintjson.dumps到底怎么选?

从适用场景来看,pprint的适用范围更广。它能处理datetime、自定义对象、集合、元组等非 JSON 类型,打印时尽量保留 Python 对象的结构感。json.dumps只支持 JSON 可序列化类型,遇到datetime会直接抛TypeError

从格式标准来看,json.dumps输出的是合法 JSON,可以直接复制到在线解析工具、文档或接口调试器中。pprint的输出更接近 Python 字面量,不适合作为交互数据。

从日志友好度看,单行 JSON 更适合日志聚合平台存储,而美化后的 JSON 适合本地调试。实际项目常用组合是:本地调试用美化 JSON,线上日志用单行 JSON。

再看一个自定义对象打印的例子:

# 文件路径:demo/dict_list_custom.py from pprint import pprint from dataclasses import dataclass @dataclass class User: name: str age: int city: str users = [ User("张三", 28, "北京"), User("李四", 32, "上海"), ] print(users) # 直接打印,依赖 __repr__ pprint(users) # pprint 同样依赖 __repr__

运行结果:

[User(name='张三', age=28, city='北京'), User(name='李四', age=32, city='上海')]

dataclass自动生成了清晰的__repr__(),所以直接打印不会出现<object at 0x...>。这说明一个工程经验:给业务对象实现清晰的__repr__(),是所有打印方案的基础。如果自定义类没有可读的__repr__(),任何美化输出的效果都会打折扣。

6. 在不同 Python 版本和终端中的差异

列表打印的基础语法在所有 Python 3 版本中基本一致,但如果你在 Python 2 的老项目或交互式环境中碰到问题,需要留意几下几点。

Python 2 中print是语句而不是函数,写法是print nums,不支持sepend参数。从 Python 3 开始,print变成了函数,这才是现代 Python 的标准写法。如果你维护老项目,看到print nums说明代码是 Python 2 风格,建议统一迁移到 Python 3。

另一个可能被忽视的差异是终端编码。在 Windows 的命令行窗口中,默认编码可能是 GBK 或 GB2312。使用print输出中文列表时,如果终端编码与 Python 标准输出的编码不一致,会报UnicodeEncodeError。常见处理方法是设置环境变量PYTHONIOENCODING=utf-8

# Windows PowerShell $env:PYTHONIOENCODING = "utf-8" python script.py # macOS / Linux PYTHONIOENCODING=utf-8 python script.py

如果你使用 VS Code 或 PyCharm,还需注意终端编码设置。多数情况下,把 Python 文件保存为 UTF-8 并在 UTF-8 终端下运行,就不会有编码问题。

我们把不同场景下的打印方式与输出效果放到一个表中:

数据形态推荐打印方式输出特征典型使用场景
小规模整型列表print(nums)print(*nums)单行本地快速验证
字符串列表print(", ".join(tags))单行无引号日志字段展示
字典列表,人眼检查pprint(users)多行缩进本地调试
字典列表,接口联调json.dumps(users, ensure_ascii=False, indent=2)标准 JSON 多行接口数据核对
字典列表,线上日志json.dumps(users, ensure_ascii=False)单行 JSON日志采集
大量元素逐条处理for循环 + 日志每条独立业务日志

7. 完整示例:一个可复用的列表打印工具函数

为了让你在真实项目里直接落地,我整理了一个工具函数,把前面讨论的多种策略组合起来。它不是复杂的类库,而是一个放在utils/print_util.py中的通用函数。

# 文件路径:utils/print_util.py import json from pprint import pprint def print_list(data, title=None, fmt="auto", ensure_ascii=False, indent=2): """通用列表打印函数。 参数: data: list 或可迭代对象。 title: 可选标题,输出前先打印。 fmt: auto / line / pretty / json / raw。 ensure_ascii: json 输出时是否转义非 ASCII 字符。 indent: json 缩进,为 None 时输出单行。 """ if hasattr(data, "__iter__") and not isinstance(data, (str, bytes, dict)): items = list(data) else: items = data if title: print(f"===== {title} =====") if fmt == "auto": if not items: print("[]") return first = items[0] if isinstance(first, dict): print(json.dumps(items, ensure_ascii=ensure_ascii, indent=indent)) else: pprint(items) elif fmt == "line": print(", ".join(str(item) for item in items)) elif fmt == "pretty": pprint(items) elif fmt == "json": print(json.dumps(items, ensure_ascii=ensure_ascii, indent=indent)) elif fmt == "raw": print(items) else: raise ValueError(f"Unsupported fmt: {fmt}")

这个函数的设计思路是:

  • fmt="auto"时自动判断:如果列表元素是字典,用 JSON 美化输出;否则用pprint
  • fmt="line"时提供单行拼接,适合元素简单且量少的情况。
  • fmt="pretty"强制使用pprint
  • fmt="json"强制使用 JSON 输出。
  • fmt="raw"相当于普通print(items)

下面是调用示例:

# 文件路径:demo/use_print_util.py from utils.print_util import print_list users = [ {"name": "张三", "age": 28, "city": "北京"}, {"name": "李四", "age": 32, "city": "上海"}, ] print_list(users, title="用户列表", fmt="json", indent=2) print() print_list([1, 2, 3], title="数字列表", fmt="line")

运行结果:

===== 用户列表 ===== [ { "name": "张三", "age": 28, "city": "北京" }, { "name": "李四", "age": 32, "city": "上海" } ] ===== 数字列表 ===== 1, 2, 3

你可以把print_list放进项目公共工具目录,在调试代码时统一调用,避免每个文件里都写自己的打印风格。也可以继续扩展,让它支持输出到指定文件、日志对象或远程日志平台。

8. 日志场景中如何打印列表

在实际后端服务里,直接使用print()打印列表通常不是最佳实践。更规范的做法是通过logging模块输出,并遵循“一条日志一条消息”的原则。因为日志系统(ELK、Splunk 等)对多行消息的处理方式不同,有些平台会把多行记录拆开,导致结构化检索失败。

推荐的做法是:在打印列表时,先把列表序列化为单行 JSON,再写入日志。

# 文件路径:demo/log_list.py import json import logging logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") logger = logging.getLogger("demo") users = [ {"name": "张三", "age": 28, "city": "北京"}, {"name": "李四", "age": 32, "city": "上海"}, ] logger.info("users=%s", json.dumps(users, ensure_ascii=False))

运行结果:

2025-01-15 10:30:00,123 INFO users=[{"name": "张三", "age": 28, "city": "北京"}, {"name": "李四", "age": 32, "city": "上海"}]

它的好处是:

  • 单行消息不会破坏日志聚合逻辑。
  • JSON 格式便于日志平台做字段解析。
  • ensure_ascii=False保证中文日志可读。

如果要从日志中还原数据,直接把 JSON 部分复制出来,用json.loads()分析即可。这里要提醒:日志中输出完整列表存在信息泄露风险。如果列表包含用户手机号、邮箱、密码等敏感信息,应该提前做脱敏处理,只打印部分字段或掩码后的内容。

另外,日志消息应该使用延迟格式化,也就是logger.info("users=%s", data_str)而不是logger.info("users=%s" % data_str)。前一种写法在日志级别不满足时不会执行字符串格式化,能减少无谓开销。

9. 运行结果与效果验证

写到这里,你可能已经复制了一部分代码。下面用一个完整案例验证效果。准备工作是在项目目录下创建两个文件:

project/ ├── utils/ │ └── print_util.py └── demo/ ├── use_print_util.py └── log_list.py

先运行use_print_util.py:

python demo/use_print_util.py

预期输出应该包含两段内容:第一段是带标题的 JSON 格式化用户列表,第二段是逗号分隔的数字列表。如果输出与预期一致,说明工具函数没问题。

再运行 log_list.py:

python demo/log_list.py

预期输出是一条包含INFO级别和时间戳的日志,后面跟着单行 JSON 数据。

如果运行失败,按下面的顺序排查:

  • 第一步:检查 Python 是否安装成功,执行python --version
  • 第二步:检查当前目录结构是否正确。utils目录下需要有__init__.py吗?Python 3 的隐式命名空间包通常不需要,但为了兼容旧项目,建议在utils目录下创建空文件__init__.py
  • 第三步:如果报ModuleNotFoundError: No module named 'utils',说明启动脚本时的工作目录不在project/下。在project/根目录运行命令即可。
  • 第四步:如果输出中文乱码,按第 6 节说明设置PYTHONIOENCODING=utf-8
效果验证清单: 1. use_print_util.py 输出 JSON 是否有缩进? 2. use_print_util.py 输出中文是否可读? 3. log_list.py 输出是否单行? 4. 修改 fmt 参数,效果是否切换? 5. 传入空列表 [] 是否正常输出?

如果以上都通过,说明你的列表打印环境已经具备生产可用性。

10. 常见问题与排查思路

下面汇总高频问题。这些问题来自日常开发中最容易踩坑的点,建议直接收藏。

问题现象可能原因排查方式解决方案
打印列表中文变成\u5f20\u4e09json.dumps()默认ensure_ascii=True查看json.dumps调用参数设置ensure_ascii=False
自定义对象显示<object at 0x...>类未实现__repr__()检查类定义使用dataclass或添加__repr__()
Windows 终端打印中文报编码错误标准输出编码与终端编码不一致查看完整报错信息设置PYTHONIOENCODING=utf-8
join()TypeError列表中含有非字符串元素检查列表类型使用生成器表达式str(item)转换
打印生成器对象只能看到内存地址生成器并未被消费为列表检查变量类型list(generator)再打印
日志平台的列表日志跨行错乱美化 JSON 包含换行符查看日志平台对多行事件的处理配置日志中使用单行 JSON
列表打印到日志后无法字段检索日志格式不规范检查日志后端解析规则结构化 JSON 日志
使用print(*list)展开超长列表报错参数数量或内联显示过大分析报错栈改用for循环或切片分批输出
控制台反复输出超长列表,滚动查看困难数据量过大明确是否需要全量输出切片list[:10]或只输出统计信息
Flask/Django 视图打印列表无输出输出被 Web 服务框架捕获查看服务日志文件使用app.logger或标准logging
打印时字典顺序和声明不一致Python 3.7 前字典无序或sort_dicts默认排序查看字典类型版本使用 Python 3.7+,需要时传sort_dicts=False

11. 性能与安全性注意事项

在深入使用列表打印后,还有两个容易被忽略的点:性能和敏感数据安全。

11.1 不要无限制打印大列表

线上代码中,如果一个列表有百万级数据,使用print()logger.info()全量输出,会占用大量 IO 和时间。更合理的方式是只打印“摘要信息”:

# 文件路径:demo/summary_list.py items = list(range(1000000)) # 不要直接 print(items) print(f"total={len(items)}, first_10={items[:10]}, last_10={items[-10:]}")

运行结果:

total=1000000, first_10=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], last_10=[999990, 999991, 999992, 999993, 999994, 999995, 999996, 999997, 999998, 999999]

在处理接口返回、分析大批量数据时,这种摘要式输出能快速帮助定位问题,又不会让控制台和日志系统承受压力。

11.2 打印对象前先脱敏

当列表元素是用户信息(用户名、手机号、身份证号、地址)时,直接全量打印并写入日志会引入信息泄露风险。通用做法是定义一个脱敏函数,在打印前替换关键字段:

# 文件路径:demo/desensitize.py import json def mask_user(user): masked = dict(user) phone = str(masked.get("phone", "")) if len(phone) == 11: masked["phone"] = phone[:3] + "****" + phone[7:] return masked users = [ {"name": "张三", "phone": "13812345678"}, {"name": "李四", "phone": "13987654321"}, ] safe_users = [mask_user(user) for user in users] print(json.dumps(safe_users, ensure_ascii=False, indent=2))

输出:

[ { "name": "张三", "phone": "138****5678" }, { "name": "李四", "phone": "139****4321" } ]

在真实项目中,脱敏函数应该被放在公共安全模块里统一使用,而不是在业务代码中临时拼字符串。这样可以避免有人绕过脱敏逻辑,把完整手机号打到日志里。

11.3 不要打印不可信的外部输入

如果列表数据来自用户上传文件、第三方接口或公网请求,打印前要谨慎。恶意构造的超长字符串可能导致控制台或日志平台存储异常。更关键的是,如果直接把外部输入拼接到日志消息中,可能造成日志注入问题,比如攻击者在字符串里嵌入换行符伪造日志记录。

处理思路是:限制单条日志的最大长度,或者对外部字符串做转义。实践中,单行日志常见做法是保留 JSON 结构,但对字段内容设置最大长度:

# 文件路径:demo/limit_log.py import json def safe_log_data(data, max_len=200): text = json.dumps(data, ensure_ascii=False) if len(text) > max_len: return text[:max_len] + "...(truncated)" return text payload = [{"content": "x" * 10000}] print(safe_log_data(payload))

输出类似:

[{"content": "xxxxxxxxxx...(truncated)

这样既保留了可读性,又防止日志被单条超长内容打爆。

12. 把打印能力融入工程实践

一个真正好用的列表打印方案,不只是写一个工具函数,还要考虑在项目中如何统一规范。这里给出几条工程建议。

12.1 统一调试辅助函数

在团队项目里创建utils/debug_utils.py,集中存放打印相关工具函数。避免每个成员都写各自的 print 风格。可以在函数内部加入环境判断,让“本地开发时打印详细内容、生产环境只打印必备日志”成为默认策略。

# 文件路径:utils/debug_utils.py import os import json from pprint import pprint DEBUG = os.getenv("DEBUG", "0") == "1" def debug_print_list(title, data): if not DEBUG: return print(f"===== {title} =====") first = data[0] if data else None if first and isinstance(first, dict): pprint(data) else: print(data)

推荐做法是让调试信息受环境变量控制,而不是每次改代码、改完又忘删。

12.2 在接口联调时返回数据检查

如果你在用 Python 写后端接口,处理course/course/list这类列表接口时,返回给前端的数据往往是一个列表。接口联调出错时,先在服务端打印响应体:

# 文件路径:demo/flask_response_debug.py from flask import Flask, jsonify app = Flask(__name__) @app.route("/course/list") def course_list(): courses = [ {"id": 1, "name": "Python 入门"}, {"id": 2, "name": "数据结构"}, ] response_data = {"code": 0, "data": courses} from utils.print_util import print_list print_list(courses, title="course list response", fmt="json", indent=2) return jsonify(response_data) if __name__ == "__main__": app.run()

如果前端报“数据为空”而你看到服务端列表有内容,问题出在序列化或字段命名上。如果服务端本身就打印空列表,问题出在查询或传参上。这个小小的打印习惯能把“接口联调问题”快速分成前后端两类。

12.3 结合数据校验

自定义对象打印之外,另一种常见场景是打印 Pydantic 模型或dataclass列表。Pydantic 模型自带model_dump()方法,可以先把对象转成字典列表再打印;dataclass可以使用dataclasses.asdict()转换。示例:

# 文件路径:demo/model_dump.py from dataclasses import dataclass, asdict @dataclass class User: name: str age: int city: str users = [User("张三", 28, "北京"), User("李四", 32, "上海")] dict_list = [asdict(user) for user in users] from pprint import pprint pprint(dict_list)

在这个设计下,业务层尽量不直接打印对象,而是先转成标准字典,再决定输出格式。这样打印逻辑与数据结构解耦,未来即使修改字段名,也只影响转换层。

12.4 自定义__repr__()是基础工程

最后,最重要的一条:给业务模型写清晰的__repr__()是回报率极高的投入。无论你用哪种打印方式,都会受益于对象本身能够自我描述。实现时不要只打印内存地址,而要把关键字段拼出来。

# 文件路径:demo/custom_repr.py class User: def __init__(self, name, age, city): self.name = name self.age = age self.city = city def __repr__(self): return f"User(name={self.name!r}, age={self.age!r}, city={self.city!r})" users = [User("张三", 28, "北京"), User("李四", 32, "上海")] print(users)

输出:

[User(name='张三', age=28, city='北京'), User(name='李四', age=32, city='上海')]

有了这样的基础,后续所有定制打印方案才能发挥真正效果。

13. 总结与后续学习方向

列表打印看起来是人人都知道的基础话题,但真正把它做对,需要理解 Python 对象格式化机制、字符串表示与容器递归输出的关系,以及不同场景(控制台、日志、接口联调)对输出格式的不同要求。表面上是“简单功能”,背后实际上是一套数据展示规范。

你在项目里可以先做三件事:第一,检查现有代码中直接print(list)的地方,评估是否有数据量过大、中文不可读、日志跨行等问题;第二,把公共的列表打印工具函数引入项目并统一调用;第三,为业务模型补上清晰的__repr__(),为敏感数据补上脱敏过滤。

继续深入学习时,可以围绕三个方向展开:一是pprint模块的更多参数与PrettyPrinter类源码;二是日志处理中的 JSON Formatter 实践;三是 Pydantic 模型在打印与序列化时的高级用法。数据展示方式看似不起眼,但真正影响着你每天定位问题的速度。

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

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

立即咨询