2025-10-08 Python 标准库 7——内置类型:二进制序列 - 详解
1. bytes 对象
bytes 是 Python 中处理二进制数据的不可变序列类型,每个元素是 0~255 范围内的整数(对应单个字节)。由于许多二进制协议基于 ASCII 设计,bytes 提供了部分与字符串(str)相似的方法,但仅适用于 ASCII 兼容场景;对于任意二进制数据,需避免盲目使用文本处理逻辑(防止数据损坏)。
1.1. bytes 的创建方式
bytes 对象可通过字面值、构造器或专用类方法创建,以下是常见方式及代码示例:
1.1.1. 字面值创建(b 前缀)
语法与字符串类似,但需添加 b 前缀,仅支持 ASCII 字符(非 ASCII 需用转义序列,如 \xXX)。支持单引号、双引号、三重引号,也可通过 r 前缀禁用转义。
# 单引号/双引号(支持嵌套)
b1 = b'hello "world"'
b2 = b'he said \'hi\''
print(b1) # 输出:b'hello "world"'
print(b2) # 输出:b'he said \'hi\''
# 三重引号(多行二进制数据)
b3 = b'''line1
line2'''
print(b3) # 输出:b'line1\nline2'(保留换行符)
# 非 ASCII 需转义(\xXX 表示十六进制字节)
b4 = b'\xe4\xb8\xad\xe6\x96\x87' # UTF-8 编码的“中文”
print(b4.decode('utf-8')) # 输出:中文(解码为字符串)
# r 前缀禁用转义
b5 = rb'C:\Users\xxx' # \x 不被当作转义
print(b5) # 输出:b'C:\\Users\\xxx'
1.1.2. 构造器创建(bytes())
构造器支持多种参数形式,灵活生成 bytes 对象:
bytes(n):创建长度为n、以 0 填充的bytes;bytes(iterable):通过 0~255 的整数可迭代对象创建;bytes(obj):通过支持缓冲区协议的对象(如bytearray、memoryview)复制创建。
# 1. 长度为 5、0 填充的 bytes
b1 = bytes(5)
print(b1) # 输出:b'\x00\x00\x00\x00\x00'
# 2. 通过整数可迭代对象(range(20) 需在 0~255 内)
b2 = bytes(range(5)) # 整数 0~4 对应字节 \x00~\x04
print(b2) # 输出:b'\x00\x01\x02\x03\x04'
# 3. 复制 bytearray 对象(缓冲区协议)
ba = bytearray(b'hello')
b3 = bytes(ba)
print(b3) # 输出:b'hello'(与 ba 内容相同,但不可变)
1.1.3. 十六进制字符串转换(bytes.fromhex())
类方法 fromhex(string) 将十六进制字符串解码为 bytes,忽略字符串中的 ASCII 空白符(空格、制表符等)。
版本变更:Python 3.7+ 支持忽略所有 ASCII 空白符(此前仅忽略空格)。
# 基础用法:十六进制字符串转 bytes
b1 = bytes.fromhex('2E f0 F1 f2') # 忽略空格,2E 对应 .,f0~f2 对应 \xf0~\xf2
print(b1) # 输出:b'.\xf0\xf1\xf2'
# 忽略多种空白符(3.7+ 支持)
b2 = bytes.fromhex('a1\tb2\nc3') # 忽略制表符、换行符
print(b2) # 输出:b'\xa1\xb2\xc3'
1.2. bytes 的核心特性与方法
1.2.1. 序列特性(索引与切片)
bytes 是序列类型,但索引返回整数(对应字节的数值,0~255),切片返回长度为 1 的 bytes 对象(与 str 索引返回字符不同)。
b = b'abc'
# 索引:返回整数(a 的 ASCII 码是 97)
print(b[0]) # 输出:97
# 切片:返回 bytes 对象
print(b[0:1]) # 输出:b'a'
# 反向索引
print(b[-1]) # 输出:99(c 的 ASCII 码)
1.2.2. 十六进制转换(bytes.hex())
实例方法 hex(sep=None, bytes_per_sep=1) 将 bytes 转换为十六进制字符串,可选参数 sep 用于添加分隔符,bytes_per_sep 控制分隔符位置(正值从右数,负值从左数)。
版本变更:Python 3.8+ 支持 sep 和 bytes_per_sep 参数。
b = b'\xf0\xf1\xf2\xf3'
# 基础用法:无分隔符
print(b.hex()) # 输出:'f0f1f2f3'
# 添加分隔符(-)
print(b.hex('-')) # 输出:'f0-f1-f2-f3'
# 控制分隔符位置(每 2 个字节加 _)
print(b.hex('_', 2)) # 输出:'f0f1_f2f3'
# 从左数每 3 个字节加空格(负值)
b2 = b'555544444c52'
print(b2.hex(' ', -4)) # 输出:'5555 4444 4c52'
1.2.3. 解码为字符串(bytes.decode())
将 bytes 按指定编码(默认 utf-8)解码为 str,errors 参数控制编码错误处理(如 strict 抛错、replace 替换为 �)。
版本变更:Python 3.9+ 在开发/调试模式下检查 errors 参数有效性。
b = b'\xe4\xb8\xad\xe6\x96\x87' # UTF-8 编码的“中文”
# 正常解码
print(b.decode('utf-8')) # 输出:中文
# 编码不匹配时处理错误
b_error = b'\xe4\xb8\xad\xe6' # 不完整的 UTF-8 字节
print(b_error.decode('utf-8', errors='replace')) # 输出:中�(替换错误字节)
2. bytearray 对象
bytearray 是 bytes 的可变版本,支持修改元素值,但元素仍需是 0~255 的整数。bytearray 无专属字面值,需通过构造器创建。
2.1. bytearray 的创建方式
2.1.1. 构造器创建(bytearray())
构造器参数与 bytes 类似,但返回可变对象:
bytearray():空bytearray;bytearray(n):长度为n、0 填充的bytearray;bytearray(iterable):通过 0~255 的整数可迭代对象创建;bytearray(obj):通过缓冲区协议对象复制创建(如bytes、str,str需指定编码,或通过bytearray(b'xxx'))。
# 1. 空 bytearray
ba1 = bytearray()
print(ba1) # 输出:bytearray(b'')
# 2. 长度为 3、0 填充
ba2 = bytearray(3)
print(ba2) # 输出:bytearray(b'\x00\x00\x00')
# 3. 整数可迭代对象
ba3 = bytearray(range(97, 100)) # 97~99 对应 a~c
print(ba3) # 输出:bytearray(b'abc')
# 4. 复制 bytes 对象
b = b'hello'
ba4 = bytearray(b)
print(ba4) # 输出:bytearray(b'hello')
2.1.2. 十六进制字符串转换(bytearray.fromhex())
与 bytes.fromhex() 功能一致,将十六进制字符串解码为 bytearray,忽略 ASCII 空白符。
版本变更:Python 3.7+ 支持忽略所有 ASCII 空白符。
ba = bytearray.fromhex('a1 b2 c3')
print(ba) # 输出:bytearray(b'\xa1\xb2\xc3')
2.2. bytearray 的可变特性
bytearray 支持修改单个元素或切片(元素需为 0~255 的整数,切片需为 bytes 或 bytearray),但无法改变长度(需通过 append、extend 等方法调整长度)。
ba = bytearray(b'abc')
# 1. 修改单个元素(97 是 a,改为 65(A))
ba[0] = 65
print(ba) # 输出:bytearray(b'Abc')
# 2. 修改切片(替换为 bytes)
ba[1:3] = b'XY'
print(ba) # 输出:bytearray(b'AXY')
# 3. 调整长度(append 单个字节,extend 多个字节)
ba.append(90) # 添加 Z(90 是 Z 的 ASCII 码)
ba.extend(b'12') # 扩展 b'12'
print(ba) # 输出:bytearray(b'AXYZ12')
# 错误:元素超出 0~255
# ba[0] = 300 # 报错:ValueError: byte must be in range(0, 256)
2.3. bytearray 的关键方法
bytearray 继承了 bytes 的大部分方法,但需注意:多数方法并非原地操作,而是返回新对象(如 replace、strip),仅 append、extend、pop 等是原地操作。
ba = bytearray(b'hello world')
# 1. replace(返回新 bytearray,原对象不变)
new_ba = ba.replace(b'world', b'python')
print(ba) # 输出:bytearray(b'hello world')(原对象不变)
print(new_ba) # 输出:bytearray(b'hello python')(新对象)
# 2. strip(移除首尾空白,返回新对象)
ba2 = bytearray(b' test ')
stripped_ba = ba2.strip()
print(stripped_ba) # 输出:bytearray(b'test')
# 3. 原地操作(pop 移除最后一个元素)
ba3 = bytearray(b'abc')
ba3.pop()
print(ba3) # 输出:bytearray(b'ab')
3. bytes 与 bytearray 的共同操作
bytes 和 bytearray 均支持通用序列操作,部分方法还支持 bytes-like object(如 bytes、bytearray、memoryview)作为参数;此外,二者还提供基于 ASCII 的文本操作(需注意仅适用于 ASCII 数据)。
3.1. 通用序列方法
以下方法适用于任意二进制数据,不依赖 ASCII 格式:
| 方法 | 功能描述 |
|---|---|
count(sub[, start[, end]]) | 统计 sub 在 [start, end] 内非重叠出现次数,sub 可为 bytes-like 或 0~255 整数 |
find(sub[, start[, end]]) | 查找 sub 的最小索引,未找到返回 -1,sub 支持 bytes-like 或整数 |
index(sub[, start[, end]]) | 同 find,但未找到抛 ValueError |
join(iterable) | 拼接 iterable 中的二进制序列,iterable 元素需为 bytes-like |
partition(sep) | 按 sep 首次出现拆分,返回 (前, sep, 后) 三元组 |
rpartition(sep) | 按 sep 最后出现拆分,返回三元组 |
removeprefix(prefix) | 移除开头的 prefix(3.9+ 新增),返回新对象 |
removesuffix(suffix) | 移除结尾的 suffix(3.9+ 新增),返回新对象 |
# 1. count:统计子序列出现次数
b = b'spam spam spam'
print(b.count(b'spam')) # 输出:3
print(b.count(115)) # 输出:3(115 是 's' 的 ASCII 码)
# 2. find:查找子序列索引
print(b.find(b'spam', 5)) # 输出:6(从索引 5 开始找)
# 3. join:拼接序列
parts = [b'hello', b' ', b'world']
print(b''.join(parts)) # 输出:b'hello world'
# 4. removeprefix(3.9+)
b2 = b'TestHook'
print(b2.removeprefix(b'Test'))# 输出:b'Hook'
3.2. 基于 ASCII 的方法
以下方法假定数据为 ASCII 格式,仅处理 0~127 的字节,非 ASCII 字节保持不变,不适用于任意二进制数据(如图片、压缩文件):
| 方法 | 功能描述 |
|---|---|
capitalize() | 首字节转为 ASCII 大写,其余转为小写 |
lower() / upper() | 所有 ASCII 字节转为小写/大写 |
swapcase() | ASCII 字节大小写互换(swapcase().swapcase() == 原对象 恒成立) |
isalnum() | 判断所有字节是否为 ASCII 字母/数字,且非空 |
isalpha() | 判断所有字节是否为 ASCII 字母,且非空 |
isdigit() | 判断所有字节是否为 ASCII 数字(0~9),且非空 |
# 1. capitalize:首字母大写
b = b'hello'
print(b.capitalize()) # 输出:b'Hello'
# 2. upper:ASCII 大写(非 ASCII 字节不变)
b2 = b'hello \xe4\xb8\xad\xe6\x96\x87' # 包含中文
print(b2.upper()) # 输出:b'HELLO \xe4\xb8\xad\xe6\x96\x87'(中文不变)
# 3. isalnum:判断 ASCII 字母/数字
print(b'isalnum()') # 输出:True(b'hello' 是 ASCII 字母)
print(b'hello123'.isalnum()) # 输出:True
print(b'hello!'.isalnum()) # 输出:False(包含 !)
3.3. printf 风格的二进制格式化
bytes 和 bytearray 支持通过 % 运算符进行类似 C 语言 printf 的格式化,适用于兼容旧代码,推荐优先使用 f-字符串(需先解码为 str)。
核心转换符
| 转换符 | 功能描述 | 示例 |
|---|---|---|
%d/%i | 十进制整数 | b'%d' % 123 → b'123' |
%x/%X | 十六进制整数(小写/大写) | b'%x' % 255 → b'ff' |
%b/%s | 二进制序列(bytes-like) | b'%b' % b'abc' → b'abc' |
%c | 单个字节(整数或 bytes) | b'%c' % 97 → b'a' |
%% | 输出 % 字符 | b'%%' → b'%' |
# 1. 基本格式化
print(b'Name: %b, Age: %d' % (b'Alice', 25)) # 输出:b'Name: Alice, Age: 25'
# 2. 十六进制与整数
print(b'Hex: %x, Oct: %o' % (255, 8)) # 输出:b'Hex: ff, Oct: 10'
# 3. 字典参数(映射键)
params = {b'name': b'Bob', b'age': 30}
print(b'Name: %(name)b, Age: %(age)d' % params) # 输出:b'Name: Bob, Age: 30'
4. memoryview 对象
memoryview 是 Python 中高效访问二进制数据的工具,通过“缓冲区协议”直接访问对象内存(如 bytes、bytearray、array.array),无需复制数据,适合处理大二进制文件或高频数据操作。
4.1. memoryview 的创建与基础特性
4.1.1. 创建方式(memoryview(obj))
obj 需支持缓冲区协议,常见对象包括 bytes、bytearray、array.array 等。
# 1. 从 bytes 创建(只读,因 bytes 不可变)
b = b'abcdef'
mv1 = memoryview(b)
print(mv1) # 输出:<memory at 0x0000021F7A8D4350>
# 2. 从 bytearray 创建(可写,因 bytearray 可变)
ba = bytearray(b'abcdef')
mv2 = memoryview(ba)
print(mv2.readonly) # 输出:False(可写)
4.1.2. 序列特性(索引与切片)
memoryview 支持索引和切片,行为与 bytes 类似:
- 索引返回整数(对应字节数值);
- 切片返回子
memoryview对象(不复制数据)。
mv = memoryview(b'abcdef')
# 1. 索引
print(mv[0]) # 输出:97(a 的 ASCII 码)
print(mv[-1]) # 输出:102(f 的 ASCII 码)
# 2. 切片(返回子 memoryview,不复制)
sub_mv = mv[1:4] # 对应 b'bcd'
print(sub_mv) # 输出:<memory at 0x0000021F7A8D4410>
print(bytes(sub_mv)) # 输出:b'bcd'(转换为 bytes 查看内容)
4.2. memoryview 的核心方法
4.2.1. 数据转换(tolist()、tobytes())
tolist():将内存数据转为 Python 列表(元素为整数,多维数据转为嵌套列表);tobytes():将内存数据转为bytes(复制数据,适合持久化或传输)。
# 1. 一维数据
mv1 = memoryview(b'abc')
print(mv1.tolist()) # 输出:[97, 98, 99]
print(mv1.tobytes()) # 输出:b'abc'
# 2. 多维数据(需先通过 cast 调整形状,见 4.2.2)
import array
arr = array.array('l', [1, 2, 3, 4]) # 'l' 表示长整数(4 字节/元素)
mv2 = memoryview(arr).cast('l', shape=[2, 2]) # 转为 2x2 多维视图
print(mv2.tolist()) # 输出:[[1, 2], [3, 4]]
4.2.2. 格式与形状转换(cast())
cast(format, shape=None) 方法将 memoryview 转换为新格式或形状,不复制数据,仅调整内存解读方式:
format:目标格式(需为struct模块支持的单一元素格式,如'B'表示无符号字节、'l'表示长整数);shape:目标形状(默认为一维,如[2, 3]表示 2 行 3 列的二维数据)。
# 1. 格式转换:长整数(l)→ 无符号字节(B)
import array
arr = array.array('l', [0x12345678]) # 1 个长整数(4 字节)
mv = memoryview(arr)
# 转为无符号字节格式(每个长整数拆为 4 个字节)
mv_byte = mv.cast('B')
print(mv_byte.tolist()) # 输出:[120, 86, 52, 18](小端序:0x78→120, 0x56→86...)
# 2. 形状转换:一维 → 二维
buf = b'12345678' # 8 字节
mv2 = memoryview(buf).cast('B', shape=[2, 4]) # 2 行 4 列
print(mv2.tolist()) # 输出:[[49, 50, 51, 52], [53, 54, 55, 56]](1~8 的 ASCII 码)
4.2.3. 内存释放(release() 与上下文管理)
memoryview 会持有底层对象的引用,调用 release() 可手动释放内存(底层对象可恢复调整大小等能力);也可通过 with 语句自动释放。
# 1. 手动 release
ba = bytearray(b'abc')
mv = memoryview(ba)
mv.release() # 释放内存
# 释放后操作报错
# print(mv[0]) # 报错:ValueError: operation forbidden on released memoryview object
# 2. with 语句自动释放
with memoryview(bytearray(b'abc')) as mv:
print(mv[0]) # 输出:97(上下文内正常使用)
# 上下文外已释放
# print(mv[0]) # 报错:ValueError: operation forbidden on released memoryview object
4.3. memoryview 关键属性
memoryview 提供多个只读属性,用于查看内存数据的元信息:
| 属性 | 功能描述 |
|---|---|
nbytes | 内存数据总字节数(product(shape) * itemsize) |
readonly | 布尔值,表明内存是否只读(如 bytes 创建的 memoryview 为 True) |
format | 数据格式(struct 风格,如 'B'、'l') |
itemsize | 单个元素的字节数(如 'l' 对应 4 或 8 字节,取决于系统) |
shape | 数据形状(元组,如 (2, 4) 表示二维) |
ndim | 数据维度(整数,如 2 表示二维) |
import array
arr = array.array('l', [1, 2, 3, 4]) # 'l' 长整数,4 字节/元素
mv = memoryview(arr).cast('l', shape=[2, 2])
print(mv.nbytes) # 输出:16(2*2*4 字节)
print(mv.readonly) # 输出:False(array 可修改)
print(mv.format) # 输出:'l'
print(mv.itemsize) # 输出:4(每个长整数 4 字节)
print(mv.shape) # 输出:(2, 2)
print(mv.ndim) # 输出:2
5. 总结
Python 二进制序列类型(bytes、bytearray、memoryview)各有定位,适用场景不同:
bytes:不可变二进制序列,适合存储固定二进制数据(如文件片段、网络数据包),支持哈希(可作为dict键);bytearray:可变二进制序列,适合动态修改二进制数据(如缓冲区、实时数据处理),但不支持哈希;memoryview:高效内存访问工具,无需复制数据,适合处理大二进制数据或高频操作(如音视频处理、科学计算)。
使用时需注意:
bytes和bytearray的方法区分“通用二进制操作”和“ASCII 专属操作”,避免用于非 ASCII 二进制数据;memoryview释放后不可再操作,推荐通过with语句管理生命周期;- 二进制格式化优先使用 f-字符串(需先解码为
str),仅兼容旧代码时使用%运算符。
浙公网安备 33010602011771号