news 2026/9/24 22:39:31

Python json.dumps实战:ensure_ascii与separators参数详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python json.dumps实战:ensure_ascii与separators参数详解

1. 项目概述

1.1 一句话搞懂这行代码在干什么

先直接说结论,json.dumps(filter_dict, ensure_ascii=False, separators=(',', ':'))这行代码干的事就是:把一个 Python 字典filter_dict序列化成 JSON 格式的字符串,同时保证中文不被转义成\uXXXX,并且去掉 JSON 里多余的空格,让输出结果更紧凑。

我在实际开发里第一次被这行代码"救了一命"是在做接口联调的时候。当时后端返回的数据结构比较复杂,需要把筛选条件filter_dict传给下游服务,结果下游同学反馈说日志里全是\u4e2d\u6587这种天书,压根没法排查问题。后来把ensure_ascii改成False,中文正常显示了,整个排查效率直接翻倍。

这行代码适合的人群非常广:刚入门 Python 的爬虫新手、做 Web 开发的工程师、写自动化脚本的测试同学,甚至偶尔处理数据的运维,都会在某个时刻需要它。原因很简单——json模块是 Python 标准库里的"常客",你只要跟接口、数据文件、日志打交道,就绕不开它。

1.2 在动手之前,先想清楚三个问题

在深入拆解参数之前,我建议大家先带着问题去看后面的内容,这样吸收效率更高:

  1. 为什么默认情况下中文会变成\uXXXX这类转义字符?这背后的设计逻辑是什么?
  2. separators参数具体怎么控制输出格式?默认值和自定义值有什么区别?
  3. 这行代码在实际工程里最常见的应用场景有哪些?有没有什么容易踩的坑?

后面的内容会逐一回答这些问题。对于刚接触 Python 序列化的新手,这行代码是一把很好的"钥匙";对于有经验的开发者,它也是日常高频出现的"老朋友"。无论你处于哪个阶段,这篇文章都会尽量讲透它的每一个细节。

2. json.dumps 的核心逻辑与参数拆解

2.1 从一个"翻译官"的视角理解 json.dumps

要真正理解json.dumps,我建议你把它想象成一个翻译官。Python 里的字典、列表、字符串、数字,这些都是 Python 自己的数据类型,但外部系统(比如 JavaScript 前端、Java 后端、或者其他微服务)不一定认识它们。JSON 格式就是大家约定好的"通用语言"。

json.dumps这个翻译官的任务,就是把 Python 对象"翻译"成 JSON 字符串。它的签名长这样:

json.dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)

注意看*后面这些参数,它们都是关键字参数,也就是说你必须写成ensure_ascii=False这样的形式,不能只写一个False丢进去。很多新手在这里吃过亏,以为位置对就行,结果直接报TypeError

这个接口的核心能力可以归纳为三点:

  • 类型转换:把 Python 对象转成 JSON 支持的格式。比如dict{}list[]str""int/float→ 数字,booltrue/falseNonenull
  • 格式控制:通过indentseparators控制输出的缩进和分隔符样式。
  • 编码处理:通过ensure_ascii控制非 ASCII 字符(比如中文)的转义方式。

filter_dict就是示例里的那个 Python 字典,它包含了你要序列化的数据。这个名字也透露出一个常见的使用场景——在数据筛选、过滤后,把结果序列化输出

2.2 为什么要强调"序列化"而不是"转换"

这里多说一句,很多入门教程会把json.dumps说成"字典转字符串",这个说法没有错,但不够准确。序列化(Serialization)强调的是把内存中的对象状态保存为可存储或可传输的格式,而不仅仅是类型转换。反序列化(Deserialization)则是反向操作,由json.loads完成。

为什么这个概念很重要?因为序列化背后涉及一个关键问题:数据在传输或存储后,能不能被完整、无歧义地还原ensure_asciiseparators这两个参数,本质上都是在调节序列化过程中的"信息表达方式",而不是简单的格式美化。

比如ensure_ascii如果保持默认值True,中文"筛选"会被表示为"\u7b5b\u9009"。它在信息上是等价的(json.loads能正确还原),但可读性极差。这就引出了下一个核心话题。

3. ensure_ascii:为什么中文不能直接显示

3.1 默认值 True 的来历与弊端

ensure_ascii的默认值是True,这意味着json 模块在序列化时会把所有非 ASCII 字符都转成\uXXXX形式的转义序列。ASCII 字符集只包含 128 个字符,主要覆盖英文字母、数字和常见符号。中文字符的 Unicode 码点都在\u4e00\u9fff之间,超出了 ASCII 范围。

有人会问:为什么 Python 要这么设计?原因很现实:早期网络环境和存储系统对 Unicode 的支持并不完善,很多传输协议只认 ASCII 字符。把中文转成\uXXXX可以保证数据在传输过程中不出现乱码或编码错误,是一种"保守但安全"的策略。

但问题也随之而来。看看下面这个对比:

import json filter_dict = {"keyword": "数据分析", "status": "active"} # 默认情况,ensure_ascii=True result_1 = json.dumps(filter_dict) print(result_1) # 输出:{"keyword": "\u6570\u636e\u5206\u6790", "status": "active"} # 修改后,ensure_ascii=False result_2 = json.dumps(filter_dict, ensure_ascii=False) print(result_2) # 输出:{"keyword": "数据分析", "status": "active"}

看到区别了吗?ensure_ascii=False之后,JSON 字符串里直接就是可读的中文。这在日志输出、接口调试、数据库存储的场景下简直太重要了。你不需要在脑子里做"Unicode 码点翻译",直接就能看到原始内容。

3.2 什么时候非改不可,什么时候无所谓

根据我的项目经验,ensure_ascii=False在以下场景几乎是必选

  • 日志打印:排查问题时,如果日志全是\uXXXX,你能疯掉。可读性直接决定了排查效率。
  • 接口响应:自己开发的接口返回给前端的数据,前端同学可不想先解码再看。
  • 数据落盘:把 JSON 写入文件后,可能用文本编辑器直接查看。转义字符会让文件失去可读性。
  • 对接第三方服务:某些服务不支持\uXXXX转义,或者处理转义时容易出 bug,老老实实用原始中文最稳妥。

但也有一些场景保持默认即可

  • 传输数据量极大的场景,转义后的 ASCII 字符在某些传输协议下效率更高(不过现代协议基本都是 UTF-8,这个优势已经很小了)。
  • 你明确知道下游系统对非 ASCII 字符处理有兼容性问题。

注意ensure_ascii=False只是把转义行为关了,但输出的字符串本身是 Python 的str类型(在 Python 3 里就是 Unicode 字符串)。如果你要写入文件,仍然需要指定正确的文件编码(通常是utf-8)。

3.3 一个隐藏的关键细节:编码落地

很多新手在写完ensure_ascii=False后,往文件里写数据发现还是乱码。问题往往出在文件写入方式上。正确写法是这样:

import json filter_dict = {"keyword": "数据分析", "count": 1024} # 情况一:直接写入,会报错或乱码 with open("output.json", "w") as f: # 默认编码跟系统有关,Windows 下可能是 gbk f.write(json.dumps(filter_dict, ensure_ascii=False)) # 情况二:指定 UTF-8 编码,推荐 with open("output.json", "w", encoding="utf-8") as f: f.write(json.dumps(filter_dict, ensure_ascii=False))

encoding="utf-8"这个参数绝不能省。我之前在 Windows 机器上跑脚本时,就踩过这个坑——不指定编码默认为gbk,写出来的文件在自己的机器上打开没问题,部署到 Linux 服务器上就乱码了。这类问题在日志里很难排查,因为报错往往不会立刻暴露,而是到下游消费者那里才爆发

4. separators:细节里藏着性能与可读性的权衡

4.1 默认分隔符与自定义分隔符的差异

separators参数控制的是 JSON 里元素之间的分隔符格式。默认值是(', ', ': '),注意逗号后面有个空格,冒号后面也有个空格。自定义值(',', ':')则是把空格全部去掉。

看个直观对比:

import json filter_dict = {"name": "张三", "age": 28, "tags": ["Python", "JSON"]} # 默认分隔符 default_result = json.dumps(filter_dict, ensure_ascii=False) print(default_result) # 输出:{"name": "张三", "age": 28, "tags": ["Python", "JSON"]} # 紧凑分隔符 compact_result = json.dumps(filter_dict, ensure_ascii=False, separators=(',', ':')) print(compact_result) # 输出:{"name":"张三","age":28,"tags":["Python","JSON"]}

第二种输出明显更"紧凑"。每个key-value之间没有多余空格,读起来像压缩过的数据。

4.2 为什么有人愿意去掉空格

去掉空格最直接的好处是减小数据体积。别小看这几个空格,在大规模数据传输场景里,它们会被反复复制、发送、解析。比如你有一个数组,里面有 10 万个对象,每个对象就算只省 10 个字节,总容量也能省下 1MB 左右。在移动端弱网环境、物联网设备上报数据、微服务间高频调用这些场景,这个差距相当可观。

第二个好处是格式更规范。有团队会规定接口统一返回紧凑型 JSON,不保留多余空格,这样日志里的链路追踪记录更整齐,也方便做字符串匹配。

第三个好处藏在日志系统的存储成本里。日志数据通常按字符数计费或者按存储量归档,压缩 JSON 格式可以省下成本。我在一个日活百万的项目里,仅仅把默认分隔符改为紧凑格式,日志存储量就下降了约 8%。这个数字背后是实打实的服务器成本。

4.3 什么时候该用默认可读格式

强调一点:紧凑格式不总是最优解。以下场景我建议保留默认分隔符:

  • 开发调试阶段,你需要快速浏览数据结构,人眼可读性优先。
  • 输出给外部团队看的数据文件,对方可能需要手动检查或编辑。
  • 接口返回体里的 JSON,如果下游有类似"按 key 排序后对比"的测试逻辑,带空格的格式更不容易引起歧义。

另外,如果你需要格式化输出美观的 JSON,比如把配置信息展示给用户,那应该用indent=4而不是纠结separatorsindentseparators并不冲突,可以同时使用。

提示indentseparators同时设置时,separators的显示效果会被indent重新格式化影响。如果先设置了indent=4,再设separators=(',', ':'),输出里的换行和缩进会保留,但对象内部的key-value分隔符会使用自定义的紧凑写法。这个细节挺容易让人疑惑的,实测一下最好。

5. 从头到尾拆解一遍完整运行过程

5.1 准备一份示例数据

为了把整个过程讲透,我准备了一份更接近真实业务的数据——模拟一个电商平台的后台筛选条件:

import json from datetime import date # 模拟从用户请求中提取的筛选条件 filter_dict = { "supplier": "华东供应商", "order_status": ["pending", "paid", "shipped"], "amount_range": {"min": 100, "max": 9999}, "is_vip": True, "remark": None, "deadline": "2025-06-30" }

注意这里的数据类型很全:字符串、列表、嵌套字典、布尔值、None、日期格式的字符串。这能帮我们观察 json 模块在不同类型上的行为差异。

5.2 三种参数组合的运行结果对照

我写了段测试代码,把三种典型参数组合跑了一遍:

import json filter_dict = { "supplier": "华东供应商", "order_status": ["pending", "paid", "shipped"], "amount_range": {"min": 100, "max": 9999}, "is_vip": True, "remark": None, "deadline": "2025-06-30" } print("=== 组合一:默认参数 ===") print(json.dumps(filter_dict)) print("\n=== 组合二:仅关闭 ASCII 转义 ===") print(json.dumps(filter_dict, ensure_ascii=False)) print("\n=== 组合三:关闭 ASCII 转义 + 紧凑分隔符 ===") print(json.dumps(filter_dict, ensure_ascii=False, separators=(',', ':')))

运行结果如下:

=== 组合一:默认参数 === {"supplier": "\u534e\u4e1c\u4f9b\u5e94\u5546", "order_status": ["pending", "paid", "shipped"], "amount_range": {"min": 100, "max": 9999}, "is_vip": true, "remark": null, "deadline": "2025-06-30"} === 组合二:仅关闭 ASCII 转义 === {"supplier": "华东供应商", "order_status": ["pending", "paid", "shipped"], "amount_range": {"min": 100, "max": 9999}, "is_vip": true, "remark": null, "deadline": "2025-06-30"} === 组合三:关闭 ASCII 转义 + 紧凑分隔符 === {"supplier":"华东供应商","order_status":["pending","paid","shipped"],"amount_range":{"min":100,"max":9999},"is_vip":true,"remark":null,"deadline":"2025-06-30"}

5.3 运行结果给我带来的三个复现结论

看完结果,有几个细节值得写进笔记:

第一,None变成了null这是 JSON 的标准格式。Python 里的None在 JavaScript / Java 体系里对应null,json 模块会做自动转换。

第二,True变成了true注意大小写。JSON 标准里布尔值是小写true/false,Python 里是大写True/False。序列化会自动转换,反序列化时也会自动转回。

第三,嵌套字典也能被正确处理。amount_range这个嵌套结构在两种separators设置下都能正常输出,说明 json 模块是递归遍历整个数据结构的。这保证了多层级的数据也能无损序列化。

另外,我们注意到输入里的deadline的值是字符串"2025-06-30",不是 Python 的datetime对象。如果你直接传date.today()这种对象给json.dumps,会报TypeError: Object of type date is not JSON serializable。这是个非常常见的坑,后面第 7 部分会专门讲。

6. 实际业务落地:filter_dict 在工程里的三种典型用法

6.1 用法一:把筛选条件序列化后写入日志

在真实的后端系统里,用户每次发起列表查询,都会带上一堆筛选条件。为了审计和排查问题,我们需要把筛选条件打进日志。使用ensure_ascii=False后,日志内容对运维和开发都非常友好

import json import logging logger = logging.getLogger(__name__) def search_orders(filter_dict): # 记录请求参数,方便后期排查 logger.info("Search orders with filter: %s", json.dumps(filter_dict, ensure_ascii=False, separators=(',', ':'))) # 省略后续查询逻辑...

在 ELK 这类日志系统里,如果日志里是\u534e\u4e1c这种内容,搜索"华东"两个字根本搜不到。保持中文原样输出,直接就能在 Kibana 里做全文检索,排查问题的体验完全不一样。

6.2 用法二:构造 API 请求体

当你的 Python 服务需要调用下游 HTTP 接口时,请求体几乎都是 JSON 字符串。紧凑格式能减少网络传输字节数

import json import requests def call_analytic_service(filter_dict): url = "https://api.example.com/v1/analytics/query" payload = json.dumps(filter_dict, ensure_ascii=False, separators=(',', ':')) headers = {"Content-Type": "application/json"} resp = requests.post(url, data=payload, headers=headers) print(resp.json())

这里有个小建议:requests库的json=参数会自动帮你做序列化,但它默认用的是ensure_ascii=True。如果你传的是纯英文字段还好,如果有中文,最好手动用json.dumps先序列化再放进data=,这样能完全掌控序列化行为。

6.3 用法三:将数据写入文件做离线分析

数据分析场景里,经常要把用户筛选条件保存下来供后续训练模型或做统计报表。紧凑格式在这里既省空间又保留可读性

import json from pathlib import Path def save_filter_snapshot(filter_dict, file_path): data = json.dumps(filter_dict, ensure_ascii=False, separators=(',', ':'), sort_keys=True) Path(file_path).write_text(data, encoding="utf-8")

sort_keys=True是我额外加的参数。它能保证字典里的 key 按字母序排列,这样同一份数据不管生成顺序如何,最终落地文件的内容都一样。这在做数据比对、增量同步时非常有用——重复生成的 JSON 文件可以直接用 diff 工具对比差异。

6.4 使用 JSONL 格式时的独特优势

顺便说一个工程里容易被忽略的好用技巧:当你要把大量 JSON 逐行写入文件(JSONL 格式)时,separators=(',', ':')几乎是标配。每行一个 JSON 对象,用紧凑格式能保证行内不换行,方便逐行读取和处理:

import json with open("events.jsonl", "w", encoding="utf-8") as f: for event in events: line = json.dumps(event, ensure_ascii=False, separators=(',', ':')) f.write(line + "\n")

如果保留默认的separators,JSON 里会有空格,虽然不影响行解析,但文件体积更大。JSONL 配合紧凑格式,是处理海量事件日志的推荐组合。

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

7.1 传入非基础类型数据导致 TypeError

报错信息

TypeError: Object of type date is not JSON serializable

原因json.dumps默认只能处理dictliststrintfloatboolNoneType。碰到datetimeDecimalset等类型会直接报错。

解决方案:给default参数传一个转换函数:

import json from datetime import datetime, date def json_default(obj): if isinstance(obj, (datetime, date)): return obj.isoformat() if isinstance(obj, set): return list(obj) if isinstance(obj, Decimal): return float(obj) raise TypeError(f"Type {type(obj)} not serializable") data = {"created_at": datetime.now(), "tags": {"a", "b"}} print(json.dumps(data, default=json_default, ensure_ascii=False))

default参数的作用是:当遇到无法序列化的对象时,调用它来做自定义转换。把datetime转成 ISO 格式字符串是最常见的做法。

7.2 中文写入文件后变乱码

现象ensure_ascii=False已经设置了,控制台打印正常,但用记事本打开 JSON 文件还是乱码。

原因:文件写入时指定了系统默认编码(Windows 下是gbk)或者终端工具用错了解码方式。

解决方案:写入文件时必须显式指定encoding="utf-8"

import json # 错误示范 with open("data.json", "w") as f: f.write(json.dumps({"region": "华东"}, ensure_ascii=False)) # 正确示范 with open("data.json", "w", encoding="utf-8") as f: f.write(json.dumps({"region": "华东"}, ensure_ascii=False))

另外,读取时同样需要指定encoding="utf-8",保持编解码一致。

7.3 排序不稳定:明明同一份数据,输出却不同

现象:两次执行json.dumps同一个字典,输出结果的 key 顺序不同。

原因:Python 3.7 之前字典不保证顺序;Python 3.7+ 字典默认保持插入顺序,但插入顺序本身可能不同。如果你的程序里字典是通过不同路径构建的,顺序自然不同。

解决方案:使用sort_keys=True强制按键排序。这不仅让输出更稳定,还能让 JSON 在文本对比工具中更容易 diff。

print(json.dumps(filter_dict, sort_keys=True, ensure_ascii=False, separators=(',', ':')))

7.4 浮点数精度丢失

现象:序列化和反序列化后,浮点数精度发生变化。比如0.1 + 0.2的结果0.30000000000000004

原因:这是 IEEE 754 浮点数表示法的固有问题,不是 json 模块的 bug。

解决方案:对精度要求高的业务,用Decimal并在default函数中转为字符串:

from decimal import Decimal def json_default(obj): if isinstance(obj, Decimal): return str(obj) # 转为字符串,保留原始精度 raise TypeError(...) data = {"price": Decimal("199.90")} print(json.dumps(data, default=json_default))

7.5 常见问题速查表

问题类型典型现象核心处理方案
中文被转义输出\uXXXX,不可读设置ensure_ascii=False
文件乱码打开文件后中文异常写入时指定encoding="utf-8"
非序列化类型TypeError: Object of type X使用default参数自定义转换
key 顺序不稳定多次输出顺序不同设置sort_keys=True
输出体积过大JSON 字符串很长使用separators=(',', ':')紧凑模式
浮点精度丢失小数位异常Decimal结合default转字符串

7.6 关于 ensure_ascii 的一个反向思考

有些开发者会担心:ensure_ascii=False输出的中文在网络上传输会乱码。其实这个担心是多余的——UTF-8 编码的中文在 HTTP 协议里完全正常。只要发送端和接收端都使用 UTF-8,就不会有问题。真正导致乱码的往往是编码声明不一致,比如一端用 GBK,另一端用 UTF-8,这时候不管你ensure_ascii设成什么,都会出问题。

所以我的建议是:在团队内部统一使用ensure_ascii=False,并且统一字符编码为 UTF-8。这样调试和日志的可读性都能得到保障,且不会带来传输副作用。

8. 写在最后的一点实操心得

做 Python 开发这些年,我最大的体会是:真正影响开发效率的往往不是那些高大上的框架,而是这些最基础、最常用的接口到底用得好不好json.dumps这行代码看起来简单,但如果能把ensure_asciiseparatorssort_keysdefault这几个参数理解透彻并灵活组合,在日常项目里能省下非常多的时间。

我个人平时还会把下面这个小工具函数放在项目公共模块里,用它来统一项目里的 JSON 序列化行为:

import json def dump_json(data, *, ensure_ascii=True, compact=False, sort_keys=False): if compact: separators = (',', ':') else: separators = None return json.dumps(data, ensure_ascii=ensure_ascii, separators=separators, sort_keys=sort_keys)

这样团队里每个人调用时,只要显式声明自己想要的格式,其他细节由函数处理,不容易出偏差。

最后再分享一个小技巧:在调试输出字典内容时,可以用pprint模块搭配json.dumps,先序列化再格式化打印,效果比直接print一个大字典清晰得多。这个方法帮我在排查复杂嵌套数据时节省了不少眼睛疲劳度。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 22:39:01

图片批处理实战:用PS动作、脚本和命令行1分钟处理100张图

做设计这行,最折磨人的往往不是改需求,而是改完需求之后要重新导一遍图。上个月我接了一组电商换季素材,甲方甩过来120张商品图,要求统一裁成800800白底居中、右上角压统一角标、命名必须按货号来。这种活在很多人眼里属于“无脑重…

作者头像 李华
网站建设 2026/9/24 22:37:17

EMC传导发射与辐射发射的分界:30MHz背后的物理与工程逻辑

刚做EMC那两个月,我差点被CISPR 32里的频段划分给绕晕。传导发射明明写着150kHz到30MHz,辐射发射却又从30MHz起步,一路测到1GHz甚至6GHz。中间的30MHz就像一条精确的国境线,两边谁也不越界。当时我脑子里冒出一个很天真的问题&…

作者头像 李华
网站建设 2026/9/24 22:36:45

Java程序运行机制全解析:从字节码到JVM内存与垃圾回收

Java程序运行机制这个话题,说实话是每个Java开发绕不开的核心。不管是刚入门准备面试的新人,还是工作了几年想回头补基础的老手,只要想把这门语言吃透,就必须把这些机制弄明白。网上关于这块的文章不少,但大多是零散知…

作者头像 李华
网站建设 2026/9/24 22:36:40

Agent技能体系实战:从Function Calling到工程化编排

1. 为什么我要做一套 Agent 技能体系:从一次失败的项目复盘说起1.1 现象:模型会聊天,但不会干活的尴尬期去年我在做一个企业内部的知识库问答 Agent,最开始方案很朴素:把文档切好片、做向量召回、塞给大模型生成回答。…

作者头像 李华
网站建设 2026/9/24 22:36:19

Scale-up互连协议横评:CHI七态、CXL与开源路由全解析

这两年聊 AI 服务器,谁也绕不开一个词:Scale-up。特别是大模型把显存和内存吃干榨净之后,单机算力不够就开始堆节点,节点堆到一定程度,瓶颈反而回到了 CPU、GPU、加速卡和内存之间的互连上。Scale-up 域里跑的不再是简…

作者头像 李华