【问题标题】:Does slicing a bytes object create a whole new copy of data in Python?切片字节对象是否会在 Python 中创建一个全新的数据副本?
【发布时间】:2020-06-23 10:51:14
【问题描述】:

假设我有非常大的字节对象(加载二进制文件后),我想逐个读取部分并推进起始位置直到它到达终点。我使用切片来完成此操作。我担心每次我请求切片时python都会创建全新的副本,而不是简单地给我指向我想要的位置的内存地址。

简单示例:

data = Path("binary-file.dat").read_bytes()
total_length = len(data)
start_pos = 0

while start_pos < total_length:
   bytes_processed = decode_bytes(data[start_pos:])  # <---- ***
   start_pos += bytes_processed 

在上面的示例中,由于切片,python 是否从 start_pos 开始创建了字节对象的全新副本。如果是这样,那么避免数据复制并仅使用指针传递到字节数组的相关位置的最佳方法是什么。

【问题讨论】:

  • 切片创建新的bytes 对象。现在,python可能不复制底层缓冲区,而是在切片之间共享一个缓冲区。但是,一般情况下是这样,它会有效地复制底层缓冲区。自己尝试一下,b = b'a'*1_000_000_000 应该会占用大量内存。现在有趣的是,如果您进行完整复制,它似乎不会复制底层缓冲区,所以b2 = b[:]然而,其他任何东西,它都会这样做,所以b3 = b[1:]
  • 如果您正在使用 bytes 并且想要一种内存高效的方式来对它们进行切片,请使用 memoryview 注意,python 没有指针
  • @juanpa.arrivillaga,谢谢我来自其他编程语言,我认为 pythin 将 byte 视为原始类型,因此可能是副本(如果它是具有不同对象类型的列表,它仍然会复制对新列表的引用)所以这在我的情况下并不理想,因为我必须每次遍历 12-20MB 的数据 4000+ 字节,并且有这样的文件流,正如你所说的 memoryview 可能是解决方案。谢谢
  • @jpnadas 是的,这提供了一些见解,但我说的是字节对象,因为它是不可变的(您无法修改数组的任何元素)我认为切片不会创建新的或复制引用。
  • @Tekz 关于列表的其他问题实际上在这里完全无关紧要。 list 对象包含其他 python 对象。 bytes 对象本质上是对原始字节缓冲区的面向对象的包装器。尽管它充当容器,但它实际上并不 包含 其他 python 对象,尽管索引返回 python 对象(实际上是整数),您可以使用其他 bytes 对象进行成员资格测试,但在内部,没有对其他python对象的引用,只是一个原始的字节缓冲区,基本上是一个char数组。

标签: python python-bytearray


【解决方案1】:

是的,对字节对象进行切片确实会创建一个副本,至少在 CPython 3.9.12 中是这样。文档中最接近承认这一点的是in the description of the bytes constructor

除了字面量形式之外,字节对象还可以通过多种其他方式创建:

  • 指定长度的零填充字节对象:bytes(10)
  • 来自整数的可迭代:bytes(range(20))
  • 通过缓冲区协议复制现有二进制数据:bytes(obj)

这表明 any 创建字节对象会创建数据的单独副本。但由于我很难找到一个明确的确认切片的作用相同,所以我求助于经验测试。

>>> b = b'\1' * 100_000_000
>>> qq = [b[1:] for _ in range(20)]

执行第一行后,python3 进程在top 的内存使用量约为 100 MB。第二次在相当长的延迟后执行,使得内存使用上升到 2G 的水平。这似乎很有定论。针对 Python 3.8 的 PyPy 7.3.9 的行为基本相同;当然,PyPy 的垃圾收集不像 CPython 那样急切,所以一旦bytes 对象变得不可访问,内存就不会被释放。

为避免复制底层缓冲区,请将您的 bytes 包装在 memoryview 中并对其进行切片:

>>> bm = memoryview(b)
>>> qq = [bm[1:] for _ in range(50)]

【讨论】:

  • 那是 CPython,对吧?如果未指定,其他实现可能会有所不同。
  • 来自PyPy documentation关于与 CPython 的差异:"相等的字符串可以共享它们的内部字符串数据,即使它们是不同的对象——即使是 unicode 字符串及其 utf8 编码的字节版本也是共享的"(谈论字符串和相等,但我可以想象它也共享字节切片的数据)。
  • 我在 PyPy 下做了同样的测试,它的行为基本相同。
猜你喜欢
  • 1970-01-01
  • 2019-10-10
  • 2018-04-05
  • 2016-10-04
  • 2021-06-25
  • 2020-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多