• 5.9 读取二进制数据到可变缓冲区中
    • 问题
    • 解决方案
    • 讨论

    5.9 读取二进制数据到可变缓冲区中

    问题

    你想直接读取二进制数据到一个可变缓冲区中,而不需要做任何的中间复制操作。或者你想原地修改数据并将它写回到一个文件中去。

    解决方案

    为了读取数据到一个可变数组中,使用文件对象的 readinto() 方法。比如:

    1. import os.path
    2.  
    3. def read_into_buffer(filename):
    4. buf = bytearray(os.path.getsize(filename))
    5. with open(filename, 'rb') as f:
    6. f.readinto(buf)
    7. return buf

    下面是一个演示这个函数使用方法的例子:

    1. >>> # Write a sample file
    2. >>> with open('sample.bin', 'wb') as f:
    3. ... f.write(b'Hello World')
    4. ...
    5. >>> buf = read_into_buffer('sample.bin')
    6. >>> buf
    7. bytearray(b'Hello World')
    8. >>> buf[0:5] = b'Hello'
    9. >>> buf
    10. bytearray(b'Hello World')
    11. >>> with open('newsample.bin', 'wb') as f:
    12. ... f.write(buf)
    13. ...
    14. 11
    15. >>>

    讨论

    文件对象的 readinto() 方法能被用来为预先分配内存的数组填充数据,甚至包括由 array 模块或 numpy 库创建的数组。和普通 read() 方法不同的是, readinto() 填充已存在的缓冲区而不是为新对象重新分配内存再返回它们。因此,你可以使用它来避免大量的内存分配操作。比如,如果你读取一个由相同大小的记录组成的二进制文件时,你可以像下面这样写:

    1. record_size = 32 # Size of each record (adjust value)
    2.  
    3. buf = bytearray(record_size)
    4. with open('somefile', 'rb') as f:
    5. while True:
    6. n = f.readinto(buf)
    7. if n < record_size:
    8. break
    9. # Use the contents of buf
    10. ...

    另外有一个有趣特性就是 memoryview ,它可以通过零复制的方式对已存在的缓冲区执行切片操作,甚至还能修改它的内容。比如:

    1. >>> buf
    2. bytearray(b'Hello World')
    3. >>> m1 = memoryview(buf)
    4. >>> m2 = m1[-5:]
    5. >>> m2
    6. <memory at 0x100681390>
    7. >>> m2[:] = b'WORLD'
    8. >>> buf
    9. bytearray(b'Hello WORLD')
    10. >>>

    使用 f.readinto() 时需要注意的是,你必须检查它的返回值,也就是实际读取的字节数。

    如果字节数小于缓冲区大小,表明数据被截断或者被破坏了(比如你期望每次读取指定数量的字节)。

    最后,留心观察其他函数库和模块中和 into 相关的函数(比如 recv_into()pack_into() 等)。Python的很多其他部分已经能支持直接的I/O或数据访问操作,这些操作可被用来填充或修改数组和缓冲区内容。

    关于解析二进制结构和 memoryviews 使用方法的更高级例子,请参考6.12小节。

    原文:

    http://python3-cookbook.readthedocs.io/zh_CN/latest/c05/p09_read_binary_data_into_mutable_buffer.html