查看: 175|回复: 1

Python字节操作进阶:bytes与bytearray核心区别与实战用法

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在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()相互转换。

同一个字符在不同编码下字节长度不同:
  1. char = '中'
  2. print(char.encode('utf-8'))   # b'\xe4\xb8\xad' (3字节)
  3. print(char.encode('gbk'))     # b'\xd6\xd0' (2字节)
  4. print(char.encode('ascii'))   # 报错UnicodeEncodeError
复制代码

str的元素是长度为1的字符串,而bytes的元素是整数:
  1. text = 'Python编程'
  2. data = text.encode('utf-8')
  3. print(data[0])   # 80 (整数)
  4. print(text[0])   # 'P' (字符串)
复制代码

二、bytes——不可变的字节序列

2.1 创建bytes的四种方式

1. 使用b前缀字面量(仅限ASCII字符或转义序列)
2. bytes()构造函数从整数序列创建
3. bytes()构造函数指定长度(全零)
4. 通过str.encode()方法
  1. # 方式一:b前缀
  2. b'hello'
  3. b'\xe4\xbd\xa0\xe5\xa5\xbd'   # '你好'的UTF-8转义
  4. # 方式二:整数列表
  5. bytes([72,101,108,108,111])   # b'Hello'
  6. # 方式三:指定长度
  7. bytes(5)   # b'\x00\x00\x00\x00\x00'
  8. # 方式四:encode
  9. 'Python编程'.encode('utf-8')  # b'Python\xe7\xbc\x96\xe7\xa8\x8b'
复制代码

2.2 基本操作(索引、切片、成员检查)
  1. data = b'hello world'
  2. print(data[0])        # 104 (整数)
  3. print(data[:5])       # b'hello'
  4. print(104 in data)    # True
  5. print(b'hello' in data) # True
  6. print(list(b'ABC'))   # [65,66,67]
复制代码

2.3 常用方法

bytes支持类似str的方法,但操作对象是字节:
  1. data = b'hello world hello python'
  2. print(data.find(b'hello'))      # 0
  3. print(data.count(b'hello'))     # 2
  4. print(data.replace(b'hello',b'hi')) # b'hi world hi python'
  5. print(b' hello '.strip())       # b'hello'
  6. print(b' '.join([b'a',b'b']))   # b'a b'
复制代码

三、bytearray——可变的字节序列

bytearray与bytes类似,但可以原地修改,适合需要频繁修改二进制数据的场景(如网络缓冲、图像处理)。
  1. ba = bytearray(b'hello')
  2. ba[0] = 106               # 改为'j' (ASCII 106)
  3. print(ba)                 # bytearray(b'jello')
  4. # bytes不可变,赋值会报错
  5. # b'hello'[0] = 106       # TypeError
复制代码

3.1 创建与修改操作
  1. ba = bytearray(b'hello world')
  2. ba[0] = ord('H')
  3. ba[6:11] = b'python'      # 切片赋值
  4. ba.append(33)             # 追加感叹号
  5. ba.extend(b' world')      # 扩展
  6. ba.insert(5, ord(','))    # 插入
  7. ba.pop()                  # 弹出末尾
  8. ba.remove(ord('a'))       # 移除第一次出现的字节值
  9. ba.reverse()              # 反转
  10. ba.clear()                # 清空
复制代码

3.2 特有方法

bytearray有copy()方法,返回独立拷贝:
  1. ba = bytearray(b'Hello')
  2. copied = ba.copy()
  3. ba[0] = 106
  4. print(ba)        # bytearray(b'jello')
  5. print(copied)    # bytearray(b'Hello')
复制代码

四、编码与解码——str与bytes的桥梁

4.1 encode()——从str到bytes
  1. text = 'Python编程'
  2. print(text.encode('utf-8'))
  3. print(text.encode('gbk'))
  4. print(text.encode('ascii', errors='replace'))  # b'Python????'
复制代码
errors参数支持strict(默认)、ignore、replace、xmlcharrefreplace、backslashreplace、namereplace。

4.2 decode()——从bytes到str
  1. data = b'Python\xe7\xbc\x96\xe7\xa8\x8b'
  2. print(data.decode('utf-8'))   # Python编程
  3. # 错误编码导致乱码或异常
  4. corrupted = b'hello\xffworld'
  5. print(corrupted.decode('utf-8', errors='replace')) # hello?world
复制代码

实际中可编写安全解码函数,尝试多种编码:
  1. def safe_decode(data):
  2.     encodings = ['utf-8','gbk','gb2312','iso-8859-1','windows-1252']
  3.     for enc in encodings:
  4.         try:
  5.             decoded = data.decode(enc)
  6.             if decoded.count('\ufffd') / max(len(decoded),1) < 0.1:
  7.                 return enc, decoded
  8.         except:
  9.             continue
  10.     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:
  1. raw = b'\xef\xbb\xbfhello'
  2. if raw.startswith(b'\xef\xbb\xbf'):
  3.     text = raw[3:].decode('utf-8')
复制代码
Python的utf-8-sig编码可自动处理BOM:
  1. with open('test.txt','w',encoding='utf-8-sig') as f:
  2.     f.write('带BOM的文字')
复制代码

五、实用场景

5.1 文件I/O——二进制模式

处理非文本文件(图片、音频等)必须使用二进制模式:
  1. with open('image.jpg','rb') as f:
  2.     data = f.read()
  3. # 分块拷贝大文件
  4. def copy_binary(src, dst):
  5.     with open(src,'rb') as fin, open(dst,'wb') as fout:
  6.         while True:
  7.             chunk = fin.read(8192)
  8.             if not chunk: break
  9.             fout.write(chunk)
复制代码
通过文件头魔数判断文件类型:
  1. def detect_type(filepath):
  2.     magic = {
  3.         b'\x89PNG\r\n\x1a\n':'PNG',
  4.         b'\xff\xd8\xff':'JPEG',
  5.         b'%PDF':'PDF',
  6.         b'PK\x03\x04':'ZIP',
  7.     }
  8.     with open(filepath,'rb') as f:
  9.         head = f.read(8)
  10.         for m,t in magic.items():
  11.             if head.startswith(m):
  12.                 return t
  13.     return '未知'
复制代码

5.2 网络编程

socket收发数据均为bytes:
  1. import socket
  2. sock = socket.socket()
  3. sock.connect(('httpbin.org',80))
  4. request = b'GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n'
  5. sock.sendall(request)
  6. response = b''
  7. while True:
  8.     chunk = sock.recv(4096)
  9.     if not chunk: break
  10.     response += chunk
  11. sock.close()
复制代码

5.3 使用struct模块打包二进制数据

处理自定义二进制协议:
  1. import struct
  2. # 打包:魔数(2B) + 消息ID(4B) + 类型(2B) + 长度(2B) + 载荷
  3. magic = b'\xAB\xCD'
  4. msg_id, msg_type, payload = 1, 0, 'Hello'
  5. payload_bytes = payload.encode('utf-8')
  6. header = struct.pack('!IHH', msg_id, msg_type, len(payload_bytes))
  7. packet = magic + header + payload_bytes
复制代码

六、常见错误与调试

常见错误:TypeError: a bytes-like object is required, not 'str'。
通常发生在需要bytes的地方传入了str,比如socket.sendall()传入字符串。解决方法是先encode()。

bytes与十六进制字符串互转:
  1. # bytes -> hex string
  2. print(b'hello'.hex())   # '68656c6c6f'
  3. # hex string -> bytes
  4. print(bytes.fromhex('68656c6c6f'))  # b'hello'
复制代码

七、总结

bytes是不可变的字节序列,bytearray是可变的。str与bytes通过encode/decode转换。处理二进制文件、网络通信、协议解析时,必须使用bytes或bytearray。掌握它们的创建、操作和编码解码细节,能避免开发中常见的类型错误和乱码问题。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python字节操作进阶:bytes与bytearray核心区别与实战用法

感谢楼主的详细教程!平时写脚本时经常遇到bytes和str混用的报错,这篇文章把底层区别和操作方法梳理得很清楚,尤其是bytearray可变性的例子很直观。关于切片赋值时长度不匹配的情况,我试过bytearray(b'hello')[1:4] = b'ABC'是可以的,但如果源长度大于目标呢?会不会自动调整bytearray的长度?希望楼主能补充一下这方面的边界情况。总之非常实用,已收藏!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-12 11:58 , Processed in 0.030916 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部