Python 中互相切换流式和数组式存取

在 Python 中,存取文件的一般操作是像下面这样的:

with open("foobar.txt", "rb+") as f:
    data = f.read(42)
    data[0:5] = b'bytes'
    f.seek(-42, 1)
    f.write(data)

对于文件操作,我们需要通过 read()write()seek() 等函数来进行流式存取。某些情况下,这种操作显得非常不便,比如说我们想修改文件中某些字节,则需要先读取出来到 bytes 中,做完修改之后 seek() 回到原来的位置,再 write()。而借助 mmap 就可以方便地完成这些操作。

import mmap
with open("foobar.txt", "rb+") as f:
    data = mmap.mmap(f.fileno(), 0)
    data[0:5] = b'bytes'

mmap 其实是 Linux 系统下的一个系统调用,作用是把文件映射到内存中,方便存取。mmap.mmap() 就完成了这样的功能,第一个参数是文件句柄,第二个参数是映射的最大长度(从文件头算起,如果为 0 则为整个文件的大小)。需要注意的是,如果要打开一个有写缓冲的文件,需要先对其 flush() 获得 mmap 对象之后,我们就可以像普通的 bytes 一样通过 slice 进行存取了。还有需要注意的是,mmap 不支持删除某一段内容:

>>> del data[0:3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: mmap object doesn't support slice deletion
mmap object doesn't support slice deletion

而有时候我们希望对 bytes 进行流式存取,这时候只需要使用 BytesIO 将其包装起来,就可以像文件一样读写了。类似的还有 StringIO 等:

from io import BytesIO
# or from six import BytesIO
stream = BytesIO(b'foobar')
stream.write(b'bar')

参考资料:

  1. https://docs.python.org/3/library/io.html
  2. https://docs.python.org/3/library/mmap.html