bytearray能就地修改而bytes不行,因为bytes是不可变对象,任何“修改”都创建新对象;bytearray是可变序列,底层直接操作同一块内存,避免反复拷贝大字节数据。

bytearray 为什么能就地修改而 bytes 不行?
因为 bytes 是不可变对象,任何“修改”操作(比如切片赋值、replace())都会创建新对象;而 bytearray 是可变序列,底层直接操作同一块内存。这点在处理大文件片段、网络缓冲区或协议解析时特别关键——避免反复拷贝几百 KB 甚至 MB 级的字节数据。
常见错误是误把 bytes 当成 bytearray 用:b = b'\x01\x02\x03'; b[0] = 0xFF → 报错 TypeError: 'bytes' object does not support item assignment
- 必须显式构造:
ba = bytearray(b'\x01\x02\x03')或ba = bytearray([1, 2, 3]) - 从文件读取时,别用
open(..., 'rb').read()直接得到bytes,改用bytearray(os.stat(path).st_size)+file.readinto()实现零拷贝加载 - 注意:
bytearray的索引、切片赋值、extend()、pop()等都真正就地生效,但ba += b'xyz'这种看似就地的操作其实在内部调用了extend(),仍是就地的;而ba = ba + b'xyz'就会新建对象
如何安全地对 bytearray 做越界写入?
直接 ba[100] = 0xFF 而 len(ba) < 101 会触发 IndexError。它不像 list 那样自动扩容,也不像 numpy.ndarray 那样支持负索引扩展。
正确做法取决于场景:
立即学习“Python免费学习笔记(深入)”;
- 已知目标长度:初始化时预留足够空间,例如
ba = bytearray(4096),后续只写入有效范围(如前 1024 字节),最后用ba[:actual_len]截取 - 动态增长且需紧凑:用
ba.append()或ba.extend(),它们会自动扩容(内部按指数增长策略,类似 list) - 需要随机位置插入:没有真正的“插入”,只能用切片赋值模拟,例如在位置
i插入b'XY'→ba[i:i] = b'XY'(这是就地的,但会移动后续所有字节)
与 memoryview 搭配使用提升性能的关键点
单独用 bytearray 已经比 bytes 快,但如果要频繁访问/修改其中某一段(比如解析二进制协议头),再套一层 memoryview 可避免重复切片拷贝。
例如解析一个 12 字节头部:
ba = bytearray(b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c') hdr = memoryview(ba)[:12] # 不拷贝,共享内存 # 修改 hdr[0] 等价于修改 ba[0] hdr[0] = 0xFF assert ba[0] == 0xFF
-
memoryview本身不可变(不能append),但它指向的bytearray可以随时增删,只要不触发 realloc(即未超出原分配容量),memoryview仍有效 - 跨函数传递时,优先传
memoryview而非切片后的bytearray,避免意外复制 - 注意:对
bytearray执行resize()(如del ba[5:])可能使已有memoryview失效,Python 3.12+ 会抛BufferError,旧版本行为未定义
哪些操作看似就地实则隐式拷贝?
不是所有方法都真正零拷贝。容易忽略的坑包括:
-
ba.decode('utf-8'):返回 str,必然拷贝并解码,若只需部分字段,改用codecs.decode(memoryview(ba)[start:end], 'utf-8') -
str(ba)或repr(ba):生成字符串表示,全量拷贝 + 格式化,调试时慎用大对象 -
ba.hex():返回新字符串,长度是原长两倍,内存开销显著;如需校验,用hashlib.sha256(ba).digest()直接处理原始字节 - 正则匹配:
re.search(b'...', ba)是就地的,但re.sub(b'...', b'repl', ba)不接受bytearray,必须转bytes→ 拷贝
真正高频就地场景(如嵌入式通信、音视频帧处理)里,尽量绕过任何涉及编码转换、格式化、正则替换的链路,用纯字节操作 + memoryview 切分来保持效率。


















