# Python 3.15 一升级，Windows 上读中文的老脚本就报错，open() 没写 encoding 怎么补？

**URL:** <https://www.shiqidu.com/t/topic/1870>\
**Category:** 开发调优\
**Tags:** Windows, Python, 编程\
**Created:** [2026 年10 月 31 日 12:53 UTC](https://www.shiqidu.com/t/topic/1870 "2026-10-31T12:53:57Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![shiqidu\_editor](https://www.shiqidu.com/user_avatar/www.shiqidu.com/shiqidu_editor/32/528_2.png) [@shiqidu\_editor](https://www.shiqidu.com/u/shiqidu_editor)\
发布日期： [2026 年10 月 31 日 12:53 UTC](https://www.shiqidu.com/t/topic/1870/1 "2026-10-31T12:53:57Z")

</div>

容易中招的多半是那种放在共享盘里好几年没人改过的小脚本：读一份当年用记事本存成 ANSI 的配置，拼几行字，再导出一份给别的老程序吃的文本。在中文 Windows 上，`open()` 不写 encoding 时一直按系统区域的编码来，也就是 GBK 那一路，所以一直没出过事。换上 Python 3.15 以后，这些脚本要么一开头就抛 UnicodeDecodeError，要么跑完了，下游打开一看全是乱码。

## 改了什么

[python.org](http://python.org) 上 3.15.0 的[发布页](https://www.python.org/downloads/release/python-3150/)写着 2026 年 10 月 9 日发布，页面顶上已经提示被 3.15.2 取代。3.15.2 是一个加急的安全更新，10 月 25 日发布。下面说的默认编码变化，是整个 3.15 系列的行为。

[What’s New in Python 3.15](https://docs.python.org/3/whatsnew/3.15.html) 里的说法是：Python 现在默认用 UTF-8，不再看系统环境，像 `open('xxx.txt')` 这样不带 encoding 的读写都按 UTF-8 走。[PEP 686](https://peps.python.org/pep-0686/) 说得更全，文件、标准输入输出和管道都一样。PEP 里也承认，Unix 上大多本来就是 UTF-8，这次主要影响的是 Windows。依赖默认编码的程序会遇到 UnicodeError、乱码，甚至数据被悄悄写坏。

最后这种很难察觉。读 GBK 文件报错，起码当场就知道。脚本写出去的文件从 GBK 变成了 UTF-8，自己读回来没问题，可换成只认本地编码的老程序去读就露馅了，那时离升级可能已经过去好几天。

## 找出没写 encoding 的地方

io 模块的[文档](https://docs.python.org/3/library/io.html)里有个可选的编码警告，3.10 起就有：运行时加 `-X warn_default_encoding`，或者设环境变量 `PYTHONWARNDEFAULTENCODING`，凡是用到默认编码的地方都会发一条 EncodingWarning。把测试或者常用的几个入口这样跑一遍，警告指向的行就是要改的地方。

除了 `open()`，下面几处也容易漏掉：

- 用 `subprocess` 加 `text=True` 读命令输出时，如果调用的是输出本地编码的 Windows 命令，现在会按 UTF-8 去解，结果就是乱码。PEP 686 给的改法是把 `text=True` 换成显式的 `encoding="utf-8"` 或 `encoding="locale"`。
- 有的代码用 `locale.getpreferredencoding()` 拿编码再往下传，可 UTF-8 模式会影响这个函数的返回值。PEP 建议改成直接写 `"utf-8"`，或者用 3.11 加进来的 `locale.getencoding()`。
- 还有 `pathlib` 的 `read_text()`，[文档](https://docs.python.org/3/library/pathlib.html)写明这个方法的参数含义和 `open()` 一样，不传 encoding 就是同一套默认编码。
- 标准流也跟着变。os 模块文档写着，UTF-8 模式下 `sys.stdin`、`sys.stdout`、`sys.stderr` 都按 UTF-8 编码。脚本输出要是被重定向到文件、再交给批处理或别的程序读，`print()` 写出去的内容编码也就变了。标准流可以单独用 `PYTHONIOENCODING` 覆盖。

另外，UTF-8 模式下 `open()` 默认仍是 strict 错误处理，遇到解不开的字节会直接抛异常，不会悄悄塞进替换字符。所以读的那一侧多半是当场报错，写的那一侧更容易在没人察觉时出事。

## 怎么补

`encoding="locale"` 是给「确实要按系统编码读写」的场景准备的，3.10 就支持了。PEP 686 提到，这个写法在 UTF-8 模式下的行为从 3.11 起已经修正，会照常按本地编码处理。跟老系统交换文件、对方改不了的那一侧，用这个写法正合适。

实在来不及改代码，可以先把 UTF-8 模式关掉，回到旧行为：设环境变量 `PYTHONUTF8=0`，或者命令行加 `-X utf8=0`。这只适合救急，比如定时任务马上要跑、代码一时改不完。

长期还是得做两件事：手里的文本文件统一转成 UTF-8，所有读写的地方都把 encoding 写明白。What’s New 里也这么建议：想在多个 Python 版本之间都保持一致，就始终显式传 encoding。

还没升级的项目也能提前摸底：UTF-8 模式从 3.7 就有了（由 PEP 540 引入，[os 模块文档](https://docs.python.org/3/library/os.html)的 Python UTF-8 Mode 一节有说明），在旧版本上把这个模式打开跑一遍测试，3.15 会出的问题大多能提前冒出来。

各位的老脚本里，没写 encoding 的地方大概有多少处？
