Python 3.15 的一项重要变化,是默认启用 UTF-8 模式。过去,同一段 open("data.txt") 代码可能在开发机上正常运行,部署到另一台使用不同区域设置的机器后却出现乱码或 UnicodeDecodeError。默认 UTF-8 能减少这类环境差异,但它并不意味着开发者可以从此忽略编码。
变化的核心:默认文本编码不再跟着系统区域设置漂移
Python 过去的默认文本编码与 locale(区域设置)密切相关。调用下面这些 API 时,如果没有传入 encoding,实际编码可能取决于操作系统、终端配置和当前 locale:
open()io.open()pathlib.Path.read_text()与write_text()- 部分接受文本文件名或文本流的库
这会产生一种典型的“只在某台机器上失败”的问题。例如,开发环境默认使用 UTF-8,而某台旧 Windows 机器使用本地代码页。同一份包含中文、重音字符或特殊符号的文件,可能得到完全不同的结果。
Python 3.15 默认启用 UTF-8 模式后,未显式指定编码的文本 I/O 通常会采用 UTF-8。这里容易混淆的一个细节是:sys.getdefaultencoding() 在 Python 3 中早已通常返回 utf-8,但过去这不代表 open() 一定使用 UTF-8。文件 I/O 的默认编码与 Python 内部字符串默认编码不是同一个概念。
三个容易答错的问题
1. 默认 UTF-8 是否意味着所有文本文件都是 UTF-8?
不是。Python 改变的是“没有指定编码时如何解释文本”,并没有转换磁盘上的旧文件。
如果合作方提供的是 Windows-1252、GB18030 或其他编码的数据,就必须按真实格式读取:
from pathlib import Path
# 按文件的真实编码修改这里,例如 cp1252、gb18030。
text = Path("legacy-data.txt").read_text(encoding="cp1252")
print(text)
不要为了让异常消失而随意使用 errors="ignore"。这会静默丢失数据,尤其不适合账单、日志、配置和导入任务。更稳妥的做法是确认数据来源的编码规范,并保留严格错误检查。
2. Python 3.15 之后还需要写 encoding="utf-8" 吗?
多数项目仍然应该写。
显式编码不仅是为了兼容旧版 Python,也是在声明文件格式。代码审查者看到下面的代码,可以立即确定仓库中的 JSON 文件必须是 UTF-8,而不用猜测运行环境:
import json
from pathlib import Path
CONFIG_FILE = Path("settings.json")
def load_config() -> dict:
with CONFIG_FILE.open("r", encoding="utf-8") as file:
return json.load(file)
def save_config(config: dict) -> None:
with CONFIG_FILE.open("w", encoding="utf-8", newline="\n") as file:
json.dump(config, file, ensure_ascii=False, indent=2)
file.write("\n")
if __name__ == "__main__":
save_config({"project": "编码检查", "enabled": True})
print(load_config())
这段程序可直接运行。它还固定了换行符,适合需要稳定生成文件、进入版本控制或跨平台比较输出的项目。
3. 默认 UTF-8 后还能显式使用 locale 编码吗?
可以。如果文件格式明确要求跟随当前系统 locale,可以使用 encoding="locale"。在 Python 3.15 中,这比省略 encoding 更能表达意图:
import locale
print("系统 locale 编码:", locale.getencoding())
with open("local-report.txt", "w", encoding="locale") as file:
file.write("This file intentionally uses the locale encoding.\n")
不过,locale 编码适合与本地旧程序交换数据,不适合作为新协议或新文件格式的默认设计。新格式通常应直接规定 UTF-8。
用命令观察并审计项目
可以创建一个探测脚本,查看当前解释器实际选择了什么编码:
# encoding_probe.py
import locale
import sys
from pathlib import Path
print("UTF-8 mode:", sys.flags.utf8_mode)
print("locale.getencoding():", locale.getencoding())
print(
"locale.getpreferredencoding(False):",
locale.getpreferredencoding(False),
)
with open("utf8-demo.txt", "w") as file:
print("open() selected:", file.encoding)
file.write("中文 café Ελληνικά\n")
print(Path("utf8-demo.txt").read_text(encoding="utf-8"))
在支持 UTF-8 模式的 Python 版本中,可以这样运行:
python -X utf8 encoding_probe.py
python -X utf8=0 encoding_probe.py
第一条命令强制启用 UTF-8 模式,可用于在 Python 3.15 之前提前验证。第二条命令用于对比关闭该模式后的行为。不过,如果操作系统 locale 本身就是 UTF-8,两次输出仍可能相同。
迁移项目时,还可以开启默认编码警告:
python -X warn_default_encoding -X utf8 your_app.py
解释器会针对部分未指定文本编码的调用发出 EncodingWarning。建议先在测试套件和命令行工具中启用,而不是直接在生产环境把所有警告升级成错误,因为第三方依赖也可能触发警告。
还可以先做一次粗略搜索:
grep -RIn --include='*.py' -E 'open\(|read_text\(|write_text\(' src tests
搜索结果需要人工分类:二进制模式 rb、wb 不需要文本编码;标准输入输出流也与普通文件调用不同;真正要关注的是省略 encoding 的文本文件操作。
升级时该怎么决定
可以按下面的顺序处理:
- 仓库内的源码、JSON、YAML、TOML、CSV 和模板文件,尽量统一为 UTF-8,并在代码中显式声明。
- 对外部文件,不要根据扩展名猜编码;依据协议、供应商文档或文件元数据处理。
- 只有明确要与本地旧程序交互时,才考虑
encoding="locale"。 - 在 CI 中运行测试时加入
-X warn_default_encoding,逐步消除无意省略编码的调用。 - 测试至少包含中文、重音字符和非拉丁文字,纯 ASCII 测试无法暴露大多数编码问题。
- 不要把
errors="ignore"当作迁移方案;确需容错时,应记录失败位置和替换策略。
Python 3.15 的默认 UTF-8 消除了一个长期存在的跨平台陷阱,但默认值只能提供安全网,不能替代文件格式契约。最稳健的代码仍然会在边界处明确回答两个问题:这些字节采用什么编码,以及遇到非法字节时应该失败、替换还是上报。