Python字节操作进阶:bytes与bytearray核心区别与实战用法
在Python开发中,处理字符串(str)时一切看起来都很自然,但当你需要读取二进制文件、解析网络数据包或进行加密计算时,就会遇到bytes和bytearray。不少初学者混淆它们与str的关系,甚至遇到类似“a bytes-like object is required”的错误。本文从底层原理出发,梳理bytes和bytearray的区别、创建与操作方式、编码解码规则,并给出文件I/O、网络编程、struct打包等典型场景的代码实践。一、为什么需要bytes?
计算机只能存储二进制数据(0和1),8个bit组成一个byte,可以表示0~255的整数。人类使用的文字、符号需要经过编码转换为字节序列才能存储和传输。Python中str是Unicode字符序列,而bytes是0~255的整数序列。两者通过encode()和decode()相互转换。
同一个字符在不同编码下字节长度不同:
char = '中'
print(char.encode('utf-8')) # b'\xe4\xb8\xad' (3字节)
print(char.encode('gbk')) # b'\xd6\xd0' (2字节)
print(char.encode('ascii')) # 报错UnicodeEncodeError
str的元素是长度为1的字符串,而bytes的元素是整数:
text = 'Python编程'
data = text.encode('utf-8')
print(data) # 80 (整数)
print(text) # 'P' (字符串)
二、bytes——不可变的字节序列
2.1 创建bytes的四种方式
1. 使用b前缀字面量(仅限ASCII字符或转义序列)
2. bytes()构造函数从整数序列创建
3. bytes()构造函数指定长度(全零)
4. 通过str.encode()方法
# 方式一:b前缀
b'hello'
b'\xe4\xbd\xa0\xe5\xa5\xbd' # '你好'的UTF-8转义
# 方式二:整数列表
bytes() # b'Hello'
# 方式三:指定长度
bytes(5) # b'\x00\x00\x00\x00\x00'
# 方式四:encode
'Python编程'.encode('utf-8')# b'Python\xe7\xbc\x96\xe7\xa8\x8b'
2.2 基本操作(索引、切片、成员检查)
data = b'hello world'
print(data) # 104 (整数)
print(data[:5]) # b'hello'
print(104 in data) # True
print(b'hello' in data) # True
print(list(b'ABC')) #
2.3 常用方法
bytes支持类似str的方法,但操作对象是字节:
data = b'hello world hello python'
print(data.find(b'hello')) # 0
print(data.count(b'hello')) # 2
print(data.replace(b'hello',b'hi')) # b'hi world hi python'
print(b' hello '.strip()) # b'hello'
print(b' '.join()) # b'a b'
三、bytearray——可变的字节序列
bytearray与bytes类似,但可以原地修改,适合需要频繁修改二进制数据的场景(如网络缓冲、图像处理)。
ba = bytearray(b'hello')
ba = 106 # 改为'j' (ASCII 106)
print(ba) # bytearray(b'jello')
# bytes不可变,赋值会报错
# b'hello' = 106 # TypeError
3.1 创建与修改操作
ba = bytearray(b'hello world')
ba = ord('H')
ba = b'python' # 切片赋值
ba.append(33) # 追加感叹号
ba.extend(b' world') # 扩展
ba.insert(5, ord(',')) # 插入
ba.pop() # 弹出末尾
ba.remove(ord('a')) # 移除第一次出现的字节值
ba.reverse() # 反转
ba.clear() # 清空
3.2 特有方法
bytearray有copy()方法,返回独立拷贝:
ba = bytearray(b'Hello')
copied = ba.copy()
ba = 106
print(ba) # bytearray(b'jello')
print(copied) # bytearray(b'Hello')
四、编码与解码——str与bytes的桥梁
4.1 encode()——从str到bytes
text = 'Python编程'
print(text.encode('utf-8'))
print(text.encode('gbk'))
print(text.encode('ascii', errors='replace'))# b'Python????'
errors参数支持strict(默认)、ignore、replace、xmlcharrefreplace、backslashreplace、namereplace。
4.2 decode()——从bytes到str
data = b'Python\xe7\xbc\x96\xe7\xa8\x8b'
print(data.decode('utf-8')) # Python编程
# 错误编码导致乱码或异常
corrupted = b'hello\xffworld'
print(corrupted.decode('utf-8', errors='replace')) # hello?world
实际中可编写安全解码函数,尝试多种编码:
def safe_decode(data):
encodings = ['utf-8','gbk','gb2312','iso-8859-1','windows-1252']
for enc in encodings:
try:
decoded = data.decode(enc)
if decoded.count('\ufffd') / max(len(decoded),1) < 0.1:
return enc, decoded
except:
continue
return 'utf-8', data.decode('utf-8', errors='replace')
4.3 BOM(字节顺序标记)
BOM用于标识编码和字节序,UTF-8 BOM为EF BB BF,UTF-16 LE BOM为FF FE,UTF-16 BE BOM为FE FF。处理文件时可检测并移除BOM:
raw = b'\xef\xbb\xbfhello'
if raw.startswith(b'\xef\xbb\xbf'):
text = raw.decode('utf-8')
Python的utf-8-sig编码可自动处理BOM:
with open('test.txt','w',encoding='utf-8-sig') as f:
f.write('带BOM的文字')
五、实用场景
5.1 文件I/O——二进制模式
处理非文本文件(图片、音频等)必须使用二进制模式:
with open('image.jpg','rb') as f:
data = f.read()
# 分块拷贝大文件
def copy_binary(src, dst):
with open(src,'rb') as fin, open(dst,'wb') as fout:
while True:
chunk = fin.read(8192)
if not chunk: break
fout.write(chunk)
通过文件头魔数判断文件类型:
def detect_type(filepath):
magic = {
b'\x89PNG\r\n\x1a\n':'PNG',
b'\xff\xd8\xff':'JPEG',
b'%PDF':'PDF',
b'PK\x03\x04':'ZIP',
}
with open(filepath,'rb') as f:
head = f.read(8)
for m,t in magic.items():
if head.startswith(m):
return t
return '未知'
5.2 网络编程
socket收发数据均为bytes:
import socket
sock = socket.socket()
sock.connect(('httpbin.org',80))
request = b'GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n'
sock.sendall(request)
response = b''
while True:
chunk = sock.recv(4096)
if not chunk: break
response += chunk
sock.close()
5.3 使用struct模块打包二进制数据
处理自定义二进制协议:
import struct
# 打包:魔数(2B) + 消息ID(4B) + 类型(2B) + 长度(2B) + 载荷
magic = b'\xAB\xCD'
msg_id, msg_type, payload = 1, 0, 'Hello'
payload_bytes = payload.encode('utf-8')
header = struct.pack('!IHH', msg_id, msg_type, len(payload_bytes))
packet = magic + header + payload_bytes
六、常见错误与调试
常见错误:TypeError: a bytes-like object is required, not 'str'。
通常发生在需要bytes的地方传入了str,比如socket.sendall()传入字符串。解决方法是先encode()。
bytes与十六进制字符串互转:
# bytes -> hex string
print(b'hello'.hex()) # '68656c6c6f'
# hex string -> bytes
print(bytes.fromhex('68656c6c6f'))# b'hello'
七、总结
bytes是不可变的字节序列,bytearray是可变的。str与bytes通过encode/decode转换。处理二进制文件、网络通信、协议解析时,必须使用bytes或bytearray。掌握它们的创建、操作和编码解码细节,能避免开发中常见的类型错误和乱码问题。
Re: Python字节操作进阶:bytes与bytearray核心区别与实战用法
感谢楼主的详细教程!平时写脚本时经常遇到bytes和str混用的报错,这篇文章把底层区别和操作方法梳理得很清楚,尤其是bytearray可变性的例子很直观。关于切片赋值时长度不匹配的情况,我试过bytearray(b'hello') = b'ABC'是可以的,但如果源长度大于目标呢?会不会自动调整bytearray的长度?希望楼主能补充一下这方面的边界情况。总之非常实用,已收藏!Re: Python字节操作进阶:bytes与bytearray核心区别与实战用法
非常感谢楼主的详细讲解!之前一直在用 bytes 和 bytearray 做网络协议解析,但对底层细节还是有些模糊。特别是“bytes 元素是整数”这一点,以前调试时总以为自己记错了。楼主把创建方式、操作方法、编解码陷阱都理得很清楚,尤其是 safe_decode 那段代码,在实际项目中非常实用。另外,bytearray 的切片赋值和 append 扩展功能,用来构建网络缓冲比手动拼接 bytes 要方便很多。希望楼主后续能再讲讲 struct 打包解包时的注意事项,或者 bytes 在 socket 收发中的常见坑。再次感谢分享!Re: Python字节操作进阶:bytes与bytearray核心区别与实战用法
感谢分享!非常清晰实用的教程,把 bytes 和 bytearray 的核心区别讲得很透彻。最近正好在做一个二进制日志解析工具,bytearray 的可变性确实省去了很多临时拷贝,配合切片赋值效率很高。另外补充一点:对于大文件读取,可以用 `memoryview` 对 bytes 或 bytearray 进行零拷贝切片,避免重复分配内存,社区里有些性能敏感的场景会这样用。再次感谢,省了我自己翻文档的时间。
页:
[1]