文章
合集Python 语言基础第 15 / 21 篇

str 与 bytes:文本、二进制与编码

核心原则

磁盘和网络只认识 bytes,应用层代码操作 str。文件打开模式决定 Python 是否自动在两者之间转换:文本模式自动转换,二进制模式由开发者手动处理。

本质区别

这是 Python 3 最核心的设计变更之一:严格区分文本(str)和二进制(bytes)。

类型strbytes
本质Unicode 字符序列原始字节序列(0–255 的整数)
存储抽象字符(不固定字节数)具体字节(一个元素 = 1 字节)
字面量前缀'text' 或 u'text'b'text'
适用场景人类可读文本、代码、JSON图片、视频、网络数据包、加密数据
长度计算len('中') → 1(字符数)len('中'.encode('utf-8')) → 3(字节数)
text   = 'Hello 世界'    # str:默认字符串类型
binary = b'Hello World'  # bytes:只接受 ASCII 字符或原始字节值

print(type(text))    # <class 'str'>
print(type(binary))  # <class 'bytes'>

编码与解码

str 和 bytes 之间必须通过明确的编码规则转换,Python 3 不做隐式转换。

str (Unicode) ──encode()──> bytes (二进制)
              <──decode()──

str → bytes:编码

text = 'Hello 世界'

# UTF-8:最通用,中文占 3 字节,英文占 1 字节
utf8_bytes = text.encode('utf-8')
print(utf8_bytes)  # b'Hello \xe4\xb8\x96\xe7\x95\x8c'

# GBK:中文 Windows 默认编码,中文占 2 字节
gbk_bytes = text.encode('gbk')
print(gbk_bytes)  # b'Hello \xc8\xc0\xbd\xe7'

# ASCII 无法编码中文
try:
    text.encode('ascii')
except UnicodeEncodeError:
    print('ASCII 不支持中文')

bytes → str:解码

raw = b'Hello \xe4\xb8\x96\xe7\x95\x8c'

# 正确解码
text = raw.decode('utf-8')
print(text)  # Hello 世界

# 解码规则不匹配,产生乱码或报错
try:
    wrong = raw.decode('gbk')
    print(wrong)  # 乱码:Hello 涓栫晫
except UnicodeDecodeError:
    print('解码失败')

文件操作中的文本模式与二进制模式

文本模式:自动编解码

# 写入时,Python 自动将 str 编码为字节后写入磁盘
with open('utf8.txt', 'w', encoding='utf-8') as f:
    f.write('Hello 世界')  # 传入 str,内部转为 b'Hello \xe4\xb8\x96\xe7\x95\x8c'

# 读取时,Python 自动将磁盘字节解码为 str
with open('utf8.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    print(type(content))  # <class 'str'>

二进制模式:直接操作 bytes

# 写入必须传入 bytes,原样写入磁盘
with open('raw.bin', 'wb') as f:
    f.write(b'\x00\x01\x02')           # 正确:写入原始字节
    # f.write('Hello')                  # 错误:TypeError
    f.write('Hello'.encode('utf-8'))    # 手动编码后写入

# 读取返回 bytes,不做任何解码
with open('raw.bin', 'rb') as f:
    data = f.read()
    print(type(data))  # <class 'bytes'>
    print(data[0])     # 0(整数)

常见陷阱

陷阱 1:混用模式导致 TypeError

# 文本模式写入 bytes
with open('test.txt', 'w') as f:
    f.write(b'Hello')  # TypeError: write() argument must be str, not bytes

# 二进制模式写入 str
with open('test.bin', 'wb') as f:
    f.write('Hello')   # TypeError: a bytes-like object is required, not 'str'

规则:

  • 传入 b'...' → 用 'rb'/'wb' 模式

  • 传入 '...' → 用 'r'/'w' 模式并指定 encoding

陷阱 2:编码不一致导致乱码

# 写入用 UTF-8
with open('config.txt', 'w', encoding='utf-8') as f:
    f.write('配置信息')

# 读取用 GBK
with open('config.txt', 'r', encoding='gbk') as f:
    print(f.read())  # 乱码:閰嶇疆淇℃伅

始终用相同的编码写入和读取同一文件。

陷阱 3:BOM 头(字节顺序标记)

Windows 记事本保存 UTF-8 文件时,可能在文件开头写入 BOM(\xef\xbb\xbf),导致读取时出现不可见字符。

# 检测并手动去除 BOM
with open('with_bom.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    if content.startswith('\ufeff'):
        content = content[1:]

# 更简洁:使用 utf-8-sig 自动处理 BOM
with open('with_bom.txt', 'r', encoding='utf-8-sig') as f:
    content = f.read()  # BOM 已自动剔除

实战场景

场景 1:读取图片并计算哈希

import hashlib

# 图片是二进制数据,必须用 'rb'
with open('photo.jpg', 'rb') as f:
    img_bytes = f.read()

md5_hash = hashlib.md5(img_bytes).hexdigest()
print(md5_hash)

场景 2:处理 HTTP 响应(requests 库)

import requests

response = requests.get('https://api.example.com/data')

raw_bytes = response.content   # bytes(原始未解码)
text_str  = response.text      # str(已按响应头编码解码)

# 自动解码失败时手动处理
try:
    text = response.content.decode('utf-8')
except UnicodeDecodeError:
    text = response.content.decode('gbk', errors='ignore')

场景 3:自定义二进制协议(文件魔数)

# 读取:前 4 字节是魔数,其余是 UTF-8 文本
with open('custom.bin', 'rb') as f:
    magic = f.read(4)
    if magic != b'MYFM':
        raise ValueError('非法文件格式')
    text = f.read().decode('utf-8')

# 写入同样格式
with open('custom.bin', 'wb') as f:
    f.write(b'MYFM')
    f.write('实际内容'.encode('utf-8'))

进阶:bytearray 可变字节序列

bytes 是不可变的(类似 tuple)。如果需要原地修改二进制数据,使用 bytearray:

ba = bytearray(b'Hello')
ba[0] = ord('J')   # 修改单个字节,变为 b'Jello'

# 常用于大文件缓冲区的原地修改
with open('data.bin', 'rb') as f:
    chunk = bytearray(f.read(1024))
    chunk[0] = 0xFF

总结速查

场景操作结果类型
文本文件读写'r'/'w' + encoding自动处理 str 与磁盘字节的转换
图片、视频、加密数据'rb'/'wb'手动处理 bytes
网络接收数据.decode('utf-8')bytes → str
网络发送数据.encode('utf-8')str → bytes
编码未知时容错errors='ignore' 或 'replace'跳过或替换无法解码的字节