str 与 bytes 的边界
Python 3 里 str 是 Unicode 码点序列,bytes 是原始字节序列,二者之间的转换必须显式发生:str.encode(编码) 得到 bytes,bytes.decode(编码) 得到 str。这个设计把编码问题的发生点暴露在代码里,代价是任何涉及输入输出的操作都需要明确编码。默认值来自运行环境的 locale:Windows 上常是 cp936,Linux 上常是 UTF-8,同一份代码在不同机器上的行为会不同。
基本信息
| 方向 | str→bytes 用 encode;bytes→str 用 decode |
|---|---|
| 默认编码 | 来自 locale.getpreferredencoding(False),随平台变化 |
| 文件读写 | open(path, encoding='utf-8') 显式指定最稳妥 |
| 标准输出 | 受 sys.stdout.encoding 影响,Windows 控制台常为 cp936 |
| UTF-8 模式 | PYTHONUTF8=1 或 python -X utf8 可让默认编码固定为 UTF-8 |
| 万能字节通道 | latin-1 能把任意字节一对一映射为字符,便于无损往返 |
典型使用场景
- 读取外部文件:来自 Windows 或老系统的文件多为 GBK,需要显式指定编码
- 网络与接口:请求体与响应体是字节,解码前必须先确认对方使用的编码
- 日志与控制台输出:中文在 Windows 控制台报编码错误,需要改输出编码或环境变量
- 字节保真处理:把二进制内容放进文本容器时用 latin-1 做无损搬运
- 跨平台脚本:固定 UTF-8 模式,避免开发机与服务器行为不一致
正确与错误对照
正确与错误对照
# 正确:两个方向显式指定编码
python -c "print('中文'.encode('utf-8'))" # b'\xe4\xb8\xad\xe6\x96\x87'
python -c "print(b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8'))" # 中文
# 显式指定文件编码,不依赖系统默认值
python -c "print(open('a.txt', encoding='utf-8').readline())"
# 读到 GBK 文件时显式声明
python -c "print(open('gbk.txt', encoding='gbk').readline())"
# 经典错误:把 bytes 直接当 str 用,结果得到 b'...' 字面量
python -c "print(str(b'abc'))" # 输出 b'abc',不是 abc
# 控制台输出编码固定(避免 Windows 下 UnicodeEncodeError)
python -X utf8 -c "print('中文')"操作与语义对照
| 操作 | 输入/输出 | 注意点 |
|---|---|---|
| str.encode('utf-8') | str → bytes | 目标编码表示不了的字符会按 errors 策略处理 |
| bytes.decode('utf-8') | bytes → str | 字节非法时按 errors 策略处理,默认抛异常 |
| open(..., encoding=) | 文本模式读写 | 不指定则用平台默认编码 |
| open(..., 'rb') / 'wb' | 二进制模式 | 得到或写入 bytes,不涉及编码 |
| len(s) / len(b) | 码点数 / 字节数 | 两者口径不同,长度校验要明确用哪个 |
| latin-1 往返 | bytes ↔ str 无损 | 用于字节保真,不作为展示编码 |
str(bytes_obj) 不会解码,它返回的是 b'...' 形式的字面量字符串,这是排查时最常见的假象;要得到文本必须用 decode。同理,b'str' 也不会自动编码。
依赖默认编码的程序在换平台后行为不同:同一份脚本在 Windows 上按 cp936 读写,在容器里按 UTF-8 读写,产出的文件可能无法互相解析。凡涉及外部输入的读写,都应显式写出编码名称。
Python 3 的 len(str) 按码点计数,与 UTF-8 字节数、显示宽度都不同;需要按字形簇计数时要引入图形分割相关库,不要用 len 直接判断用户可见字符数。