bigywater.tech
新版本特性 · 6

升级 Python 3.15 后 open() 读老文件报 UnicodeDecodeError:默认 UTF-8 让 GBK 文件读不出来,read_text、configparser、subprocess 一起变

“升级 Python 3.15 后 open() 读老文件报 UnicodeDecodeError: ‘utf-8’ codec can’t decode byte” 是 GBK 环境(典型的中文 Windows)升级后最先遇到的问题。这篇在本机用 LC_ALL 模拟 GBK、ISO8859-1 等语言环境,用 3.14.9 和 3.15.0 并排复现:哪些接口跟着变,老文件怎么读,退路是什么。

utf-8
3.15 里 open() 不写 encoding 时在所有语言环境下的默认编码(3.14 在 GBK 环境下是 GBK)
4 个接口
open、Path.read_text、configparser.read、subprocess(text=True) 在 3.15 里都按 utf-8,GBK 文件都读不了
PYTHONUTF8=0
把 3.15 退回按系统语言环境的开关,-X utf8=0 等价

默认编码

实测 · 本机执行输出
  LC_ALL=C:
      3.14: utf8_mode = 1 | open().encoding = utf-8 | locale.getencoding() = US-ASCII | sys.stdout.encoding = utf-8
      3.15: utf8_mode = 1 | open().encoding = utf-8 | locale.getencoding() = US-ASCII | sys.stdout.encoding = utf-8
  LC_ALL=en_US.ISO8859-1:
      3.14: utf8_mode = 0 | open().encoding = ISO8859-1 | locale.getencoding() = ISO8859-1 | sys.stdout.encoding = iso8859-1
      3.15: utf8_mode = 1 | open().encoding = utf-8 | locale.getencoding() = ISO8859-1 | sys.stdout.encoding = utf-8
  LC_ALL=zh_CN.GBK:
      3.14: utf8_mode = 0 | open().encoding = GBK | locale.getencoding() = GBK | sys.stdout.encoding = gbk
      3.15: utf8_mode = 1 | open().encoding = utf-8 | locale.getencoding() = GBK | sys.stdout.encoding = utf-8
  LC_ALL=zh_CN.UTF-8:
      3.14: utf8_mode = 0 | open().encoding = UTF-8 | locale.getencoding() = UTF-8 | sys.stdout.encoding = utf-8
      3.15: utf8_mode = 1 | open().encoding = utf-8 | locale.getencoding() = UTF-8 | sys.stdout.encoding = utf-8

3.15 在所有语言环境下 UTF-8 模式默认开启;3.14 里只有 C 区域会这样。

老文件

实测 · 本机执行输出
  3.14 写(LC_ALL=zh_CN.GBK)(退出码 0):
      写入的字节 = b'\xd6\xd0\xce\xc4:\xc4\xe3\xba\xc3'
  3.14 读同一个文件(退出码 0):
      读出来 = 中文:你好
  3.15 读同一个文件(退出码 0):
      UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuation byte
  3.15 写(同样的 LC_ALL=zh_CN.GBK)(退出码 0):
      写入的字节 = b'\xe4\xb8\xad\xe6\x96\x87:\xe4\xbd\xa0\xe5\xa5\xbd'
  3.14 读 3.15 写的文件(退出码 0):
      UnicodeDecodeError: 'gbk' codec can't decode byte 0xad in position 2: illegal multibyte sequence

GBK 环境下 3.14 写的文件在 3.15 里读 UnicodeDecodeError;3.15 写的文件被 3.14 读也是 UnicodeDecodeError。

标准输出

实测 · 本机执行输出
  Python 3.14(LC_ALL=zh_CN.GBK)(退出码 1):
      stdout encoding = gbk
      UnicodeEncodeError: 'gbk' codec can't encode character '\U0001f600' in position 0: illegal multibyte sequence
  Python 3.15(LC_ALL=zh_CN.GBK)(退出码 0):
      stdout encoding = utf-8
      😀 你好

3.15 的 sys.stdout.encoding 也是 utf-8,GBK 环境下 print emoji 不再崩。

受影响的接口

实测 · 本机执行输出
  Python 3.14(LC_ALL=zh_CN.GBK)(退出码 0):
      open("gbk.txt").read()                         -> '中文:你好\n'
      open("gbk.txt", encoding="gbk").read()         -> '中文:你好\n'
      open("gbk.txt", encoding="locale").read()      -> '中文:你好\n'
      pathlib.Path("gbk.txt").read_text()            -> '中文:你好\n'
      configparser.ConfigParser().read("gbk.ini")    -> {'k': '中文'}
      subprocess.run([...child.py], text=True).stdout -> '子进程输出:你好\n'
      subprocess.run([...child.py], text=True, encoding="gbk") -> '子进程输出:你好\n'
  Python 3.15(LC_ALL=zh_CN.GBK)(退出码 0):
      open("gbk.txt").read()                         -> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuati
      open("gbk.txt", encoding="gbk").read()         -> '中文:你好\n'
      open("gbk.txt", encoding="locale").read()      -> '中文:你好\n'
      pathlib.Path("gbk.txt").read_text()            -> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuati
      configparser.ConfigParser().read("gbk.ini")    -> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 8: invalid continuati
      subprocess.run([...child.py], text=True).stdout -> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd7 in position 0: invalid continuati
      subprocess.run([...child.py], text=True, encoding="gbk") -> '子进程输出:你好\n'

不写 encoding 的 open、read_text、configparser、subprocess(text=True) 都变;显式写 encoding 的不变。

退路

实测 · 本机执行输出
  3.15,LC_ALL=zh_CN.GBK PYTHONUTF8=0(退出码 0):
      utf8_mode = 0 | open().encoding = GBK | locale.getencoding() = GBK | sys.stdout.encoding = gbk
  3.15,LC_ALL=zh_CN.GBK -X utf8=0(退出码 0):
      utf8_mode = 0 | open().encoding = GBK | locale.getencoding() = GBK | sys.stdout.encoding = gbk
  3.15,PYTHONUTF8=0 时读 3.14 写的 GBK 文件(退出码 0):
      读出来 = 中文:你好
  Python 3.14 -X warn_default_encoding(退出码 0):
      跑完
      <tmp>/warn.py:5: EncodingWarning: 'encoding' argument not specified
  Python 3.15 -X warn_default_encoding(退出码 0):
      跑完
      <tmp>/warn.py:5: EncodingWarning: 'encoding' argument not specified

PYTHONUTF8=0 或 -X utf8=0 退回旧行为;-X warn_default_encoding 提前找出没写 encoding 的 open。

分步演示

默认编码实测
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Homebrew 的 Python 3.14.9 与 3.15.0(同一台机器上两个解释器并排跑同一份代码;两者都不是自由线程构建,也都没有启用 JIT);每个例子是临时目录里的独立小脚本,用子进程运行并取真实的 stdout / stderr(路径换成 );计时用 time.perf_counter,数字每次运行会有小幅抖动。语言环境用环境变量 LC_ALL 模拟(zh_CN.GBK、en_US.ISO8859-1、zh_CN.UTF-8、C),在这台 macOS 上它们都能被 Python 识别;为了能把子进程的输出收回来,有几个例子加了 PYTHONIOENCODING=utf-8,只影响标准输出,不影响 open() 的默认编码
    • 没有验证:真正的 Windows(GBK 代码页、控制台编码、sys.getfilesystemencoding 的行为);Linux 上其他语言环境;os.environ、命令行参数、文件名在 3.15 UTF-8 模式下的编码;pip、setuptools 等工具读取 GBK 文件的表现;ctypes、locale.setlocale 等直接调用 C 的接口;PEP 686 的完整规范(本篇只验证了行为)
    京ICP备2026067502号-1京公网安备11010502063487号