首页 文章

读取二进制文件并循环遍历每个字节

提问于
浏览
294

在Python中,如何读取二进制文件并循环遍历该文件的每个字节?

9 回答

  • 308

    Python 3,一次读取所有文件:

    with open("filename", "rb") as binary_file:
        # Read the whole file at once
        data = binary_file.read()
        print(data)
    

    您可以使用 data 变量迭代任何您想要的内容 .

  • 10

    如果要读取大量二进制数据,可能需要考虑struct module . 它被记录为转换"between C and Python types",但当然,字节是字节,并且这些是否是作为C类型创建并不重要 . 例如,如果二进制数据包含两个2字节整数和一个4字节整数,则可以按如下方式读取它们(示例来自 struct 文档):

    >>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
    (1, 2, 3)
    

    您可能会发现这比显式循环文件内容更方便,更快捷或两者兼而有之 .

  • 46

    Python 2.4 and Earlier

    f = open("myfile", "rb")
    try:
        byte = f.read(1)
        while byte != "":
            # Do stuff with byte.
            byte = f.read(1)
    finally:
        f.close()
    

    Python 2.5-2.7

    with open("myfile", "rb") as f:
        byte = f.read(1)
        while byte != "":
            # Do stuff with byte.
            byte = f.read(1)
    

    请注意,with语句在2.5以下的Python版本中不可用 . 要在v 2.5中使用它,您需要导入它:

    from __future__ import with_statement
    

    在2.6中,这不是必需的 .

    Python 3

    在Python 3中,它有点不同 . 我们将不再以字节模式从流中获取原始字符,而是字节对象,因此我们需要更改条件:

    with open("myfile", "rb") as f:
        byte = f.read(1)
        while byte != b"":
            # Do stuff with byte.
            byte = f.read(1)
    

    或者正如benhoyt所说,跳过不等于并利用 b"" 评估为假的事实 . 这使代码在2.6和3.x之间兼容,无需任何更改 . 如果从字节模式转换为文本或反向,它还可以避免更改条件 .

    with open("myfile", "rb") as f:
        byte = f.read(1)
        while byte:
            # Do stuff with byte.
            byte = f.read(1)
    
  • 3

    此生成器从文件中生成字节,以块的形式读取文件:

    def bytes_from_file(filename, chunksize=8192):
        with open(filename, "rb") as f:
            while True:
                chunk = f.read(chunksize)
                if chunk:
                    for b in chunk:
                        yield b
                else:
                    break
    
    # example:
    for b in bytes_from_file('filename'):
        do_stuff_with(b)
    

    有关iteratorsgenerators的信息,请参阅Python文档 .

  • 18

    如果文件不是太大,将其保存在内存中就是一个问题:

    bytes_read = open("filename", "rb").read()
    for b in bytes_read:
        process_byte(b)
    

    其中process_byte表示要对传入的字节执行的某些操作 .

    如果要一次处理一个块:

    file = open("filename", "rb")
    try:
        bytes_read = file.read(CHUNKSIZE)
        while bytes_read:
            for b in bytes_read:
                process_byte(b)
            bytes_read = file.read(CHUNKSIZE)
    finally:
        file.close()
    
  • 1

    要读取文件 - 一次一个字节(忽略缓冲) - 您可以使用two-argument iter(callable, sentinel) built-in function

    with open(filename, 'rb') as file:
        for byte in iter(lambda: file.read(1), b''):
            # Do stuff with byte
    

    它调用 file.read(1) 直到它不返回 b'' (空字节串) . 对于大文件,内存不会无限增长 . 您可以将 buffering=0 传递给 open() ,以禁用缓冲 - 它保证每次迭代只读取一个字节(慢) .

    with -statement自动关闭文件 - 包括下面的代码引发异常的情况 .

    尽管默认情况下存在内部缓冲,但一次处理一个字节仍然是低效的 . 例如,这是 blackhole.py 实用程序,它会占用它给出的所有内容:

    #!/usr/bin/env python3
    """Discard all input. `cat > /dev/null` analog."""
    import sys
    from functools import partial
    from collections import deque
    
    chunksize = int(sys.argv[1]) if len(sys.argv) > 1 else (1 << 15)
    deque(iter(partial(sys.stdin.detach().read, chunksize), b''), maxlen=0)
    

    例:

    $ dd if=/dev/zero bs=1M count=1000 | python3 blackhole.py
    

    它在我的机器上 chunksize == 32768 时处理~1.5 GB / s,在 chunksize == 1 时仅处理~7.5 MB / s . 也就是说,一次读取一个字节要慢200倍 . 如果您可以重写处理以一次使用多个字节并且需要性能,请将其考虑在内 .

    mmap允许您同时将文件视为bytearray和文件对象 . 如果您需要访问两个接口,它可以作为将整个文件加载到内存中的替代方法 . 特别是,您可以使用普通的 for -loop在内存映射文件上一次迭代一个字节:

    from mmap import ACCESS_READ, mmap
    
    with open(filename, 'rb', 0) as f, mmap(f.fileno(), 0, access=ACCESS_READ) as s:
        for byte in s: # length is equal to the current file size
            # Do stuff with byte
    

    mmap 支持切片表示法 . 例如, mm[i:i+len] 从位置 i 开始的文件返回 len 个字节 . Python 3.2之前不支持上下文管理器协议;在这种情况下,您需要显式调用 mm.close() . 使用 mmap 迭代每个字节会消耗比 file.read(1) 更多的内存,但 mmap 的速度要快一个数量级 .

  • 143

    总结一下chrispy,Skurmedel,Ben Hoyt和Peter Hansen的所有优点,这将是一次一个字节处理二进制文件的最佳解决方案:

    with open("myfile", "rb") as f:
        while True:
            byte = f.read(1)
            if not byte:
                break
            do_stuff_with(ord(byte))
    

    对于Python 2.6及更高版本,因为:

    内部

    • python缓冲区 - 无需读取块

    • 干燥原理 - 不要重复读取线

    • with语句可确保关闭文件
      当没有更多字节时

    • 'byte'的计算结果为false(而不是字节为零时)

    或者使用J. F. Sebastians解决方案来提高速度

    from functools import partial
    
    with open(filename, 'rb') as file:
        for byte in iter(partial(file.read, 1), b''):
            # Do stuff with byte
    

    或者如果你想将它作为代码表示的生成器函数:

    def bytes_from_file(filename):
        with open(filename, "rb") as f:
            while True:
                byte = f.read(1)
                if not byte:
                    break
                yield(ord(byte))
    
    # example:
    for b in bytes_from_file('filename'):
        do_stuff_with(b)
    
  • 27

    在Python中读取二进制文件并循环遍历每个字节

    Python 3.5中的新功能是 pathlib 模块,它有一个特别方便的方法来读取文件中的字节,允许我们迭代字节 . 我认为这是一个体面的(如果快速和肮脏)答案:

    import pathlib
    
    for byte in pathlib.Path(path).read_bytes():
        print(byte)
    

    有趣的是,这是唯一提到 pathlib 的答案 .

    在Python 2中,你可能会这样做(正如Vinay Sajip也建议的那样):

    with open(path, 'b') as file:
        for byte in file.read():
            print(byte)
    

    如果文件可能太大而无法在内存中进行迭代,那么您可以使用带有 callable, sentinel 签名的 iter 函数(Python 2版本)以惯用方式对其进行分块:

    with open(path, 'b') as file:
        callable = lambda: file.read(1024)
        sentinel = bytes() # or b''
        for chunk in iter(callable, sentinel): 
            for byte in chunk:
                print(byte)
    

    (其他几个答案提到了这一点,但很少有人提供合理的读取大小 . )

    大文件或缓冲/交互式阅读的最佳实践

    让我们创建一个执行此操作的函数,包括Python 3.5标准库的惯用用法:

    from pathlib import Path
    from functools import partial
    from io import DEFAULT_BUFFER_SIZE
    
    def file_byte_iterator(path):
        """given a path, return an iterator over the file
        that lazily loads the file
        """
        path = Path(path)
        with path.open('rb') as file:
            reader = partial(file.read1, DEFAULT_BUFFER_SIZE)
            file_iterator = iter(reader, bytes())
            for chunk in file_iterator:
                for byte in chunk:
                    yield byte
    

    请注意,我们使用 file.read1 . file.read 阻塞,直到它获得所请求的所有字节或 EOF . file.read1 允许我们避免阻塞,因此可以更快地返回 . 没有其他答案也提到这一点 .

    演示最佳实践用法:

    让我们创建一个具有兆字节(实际上是mebibyte)的伪随机数据的文件:

    import random
    import pathlib
    path = 'pseudorandom_bytes'
    pathobj = pathlib.Path(path)
    
    pathobj.write_bytes(
      bytes(random.randint(0, 255) for _ in range(2**20)))
    

    现在让我们迭代它并在内存中实现它:

    >>> l = list(file_byte_iterator(path))
    >>> len(l)
    1048576
    

    我们可以检查数据的任何部分,例如,最后100个字节和前100个字节:

    >>> l[-100:]
    [208, 5, 156, 186, 58, 107, 24, 12, 75, 15, 1, 252, 216, 183, 235, 6, 136, 50, 222, 218, 7, 65, 234, 129, 240, 195, 165, 215, 245, 201, 222, 95, 87, 71, 232, 235, 36, 224, 190, 185, 12, 40, 131, 54, 79, 93, 210, 6, 154, 184, 82, 222, 80, 141, 117, 110, 254, 82, 29, 166, 91, 42, 232, 72, 231, 235, 33, 180, 238, 29, 61, 250, 38, 86, 120, 38, 49, 141, 17, 190, 191, 107, 95, 223, 222, 162, 116, 153, 232, 85, 100, 97, 41, 61, 219, 233, 237, 55, 246, 181]
    >>> l[:100]
    [28, 172, 79, 126, 36, 99, 103, 191, 146, 225, 24, 48, 113, 187, 48, 185, 31, 142, 216, 187, 27, 146, 215, 61, 111, 218, 171, 4, 160, 250, 110, 51, 128, 106, 3, 10, 116, 123, 128, 31, 73, 152, 58, 49, 184, 223, 17, 176, 166, 195, 6, 35, 206, 206, 39, 231, 89, 249, 21, 112, 168, 4, 88, 169, 215, 132, 255, 168, 129, 127, 60, 252, 244, 160, 80, 155, 246, 147, 234, 227, 157, 137, 101, 84, 115, 103, 77, 44, 84, 134, 140, 77, 224, 176, 242, 254, 171, 115, 193, 29]
    

    不要按行迭代二进制文件

    不要执行以下操作 - 这会拉出任意大小的块直到它到达换行符 - 当块太小时太慢,并且可能太大:

    with open(path, 'rb') as file:
            for chunk in file: # text newline iteration - not for bytes
                for byte in chunk:
                    yield byte
    

    以上只适用于语义上人类可读的文本文件(如纯文本,代码,标记,降价等...基本上任何ascii,utf,latin等编码) .

  • 3

    如果你正在寻找快速的东西,这是我一直使用的方法多年来一直工作:

    from array import array
    
    with open( path, 'rb' ) as file:
        data = array( 'B', file.read() ) # buffer the file
    
    # evaluate it's data
    for byte in data:
        v = byte # int value
        c = chr(byte)
    

    如果你想迭代字符而不是整数,你可以简单地使用 data = file.read() ,它应该是py3中的bytes()对象 .

相关问题