Python 3.15 默认启用 UTF-8:文件读写终于一致,但 encoding 仍不能省

2026-09-02 38 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

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

搜索结果需要人工分类:二进制模式 rbwb 不需要文本编码;标准输入输出流也与普通文件调用不同;真正要关注的是省略 encoding 的文本文件操作。

升级时该怎么决定

可以按下面的顺序处理:

  • 仓库内的源码、JSON、YAML、TOML、CSV 和模板文件,尽量统一为 UTF-8,并在代码中显式声明。
  • 对外部文件,不要根据扩展名猜编码;依据协议、供应商文档或文件元数据处理。
  • 只有明确要与本地旧程序交互时,才考虑 encoding="locale"
  • 在 CI 中运行测试时加入 -X warn_default_encoding,逐步消除无意省略编码的调用。
  • 测试至少包含中文、重音字符和非拉丁文字,纯 ASCII 测试无法暴露大多数编码问题。
  • 不要把 errors="ignore" 当作迁移方案;确需容错时,应记录失败位置和替换策略。

Python 3.15 的默认 UTF-8 消除了一个长期存在的跨平台陷阱,但默认值只能提供安全网,不能替代文件格式契约。最稳健的代码仍然会在边界处明确回答两个问题:这些字节采用什么编码,以及遇到非法字节时应该失败、替换还是上报。


相关推荐