“升级 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 并排复现:哪些接口跟着变,老文件怎么读,退路是什么。
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-83.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 sequenceGBK 环境下 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 specifiedPYTHONUTF8=0 或 -X utf8=0 退回旧行为;-X warn_default_encoding 提前找出没写 encoding 的 open。