【问题标题】:What is the idiomatic way to iterate over a binary file?迭代二进制文件的惯用方法是什么?
【发布时间】:2011-06-01 18:39:08
【问题描述】:

使用文本文件,我可以这样写:

with open(path, 'r') as file:
    for line in file:
        # handle the line

这等价于:

with open(path, 'r') as file:
    for line in iter(file.readline, ''):
        # handle the line

PEP 234 中记录了此成语,但我未能找到二进制文件的类似成语。

使用二进制文件,我可以这样写:

with open(path, 'rb') as file:
    while True:
        chunk = file.read(1024 * 64)
        if not chunk:
            break
        # handle the chunk

我尝试过与文本文件相同的习语:

def make_read(file, size):
    def read():
        return file.read(size)
    return read

with open(path, 'rb') as file:
    for chunk in iter(make_read(file, 1024 * 64), b''):
        # handle the chunk

这是在 Python 中迭代二进制文件的惯用方式吗?

【问题讨论】:

    标签: python file iterator


    【解决方案1】:

    这个问题已经过去了将近 10 年,现在 Python 3.8 有了 PEP 572 中描述的 := Walrus Operator

    要以惯用和富有表现力的方式(使用 Python 3.8 或更高版本)读取块中的文件,您可以这样做:

    # A loop that cannot be trivially rewritten using 2-arg iter().
    while chunk := file.read(1024 * 64):
        process(chunk)
    

    【讨论】:

    • 我得到了 while chunk := input_file.read(1024 * 64): ^ SyntaxError: invalid syntax
    • 你使用的是 Python 3.8+ 吗?
    【解决方案2】:

    迭代读取二进制文件的 Pythonic 方法是使用内置函数 iter带有两个参数和标准函数 functools.partial,如 Python library documentation 中所述: p>

    iter(对象[, 哨兵​​m>])

    返回一个迭代器对象。根据第二个参数的存在,第一个参数的解释非常不同。如果没有第二个参数,object 必须是支持迭代协议(__iter__() 方法)的集合对象,或者它必须支持序列协议(__getitem__() 方法,整数参数以 @ 开头987654327@)。如果它不支持这些协议中的任何一个,则会引发TypeError。如果给定第二个参数 sentinel,则 object 必须是可调用对象。在这种情况下创建的迭代器将调用 object,每次调用其 __next__() 方法时不带任何参数;如果返回的值等于sentinel,则StopIteration 将被提升,否则将返回该值。

    另请参阅迭代器类型。

    iter() 的第二种形式的一个有用应用是构建一个块阅读器。例如,从二进制数据库文件中读取固定宽度的块,直到到达文件末尾:

    from functools import partial
    
    with open('mydata.db', 'rb') as f:
        for block in iter(partial(f.read, 64), b''):
            process_block(block)
    

    【讨论】:

      【解决方案3】:

      试试:

      >>> with open('dups.txt','rb') as f:
      ...    for chunk in iter((lambda:f.read(how_many_bytes_you_want_each_time)),''):
      ...       i+=1
      

      iter 需要一个零参数的函数。

      • 普通的f.read 会读取整个文件,因为缺少size 参数;
      • f.read(1024) 表示调用一个函数并将其返回值(从文件加载的数据)传递给iter,所以iter 根本没有得到一个函数;
      • (lambda:f.read(1234)) 是一个接受零参数(lambda: 之间没有任何参数)并调用 f.read(1234) 的函数。

      以下之间存在等价关系:

      somefunction = (lambda:f.read(how_many_bytes_you_want_each_time))
      

      def somefunction(): return f.read(how_many_bytes_you_want_each_time)
      

      在你的代码之前有一个,你可以写:iter(somefunction, '')

      从技术上讲,您可以跳过 lambda 周围的括号,python 的语法会接受。

      【讨论】:

      • 是的,使用 iter() 的哨兵技巧真的很巧妙! (虽然我不喜欢 lambdas,所以我会做一个函数)。
      • 行得通!谢谢。放弃旧习语 (Perl) 并学习新习语,同时仍然保持合理的生产力是很困难的。
      • 这行得通...但在我看来有点难以阅读。
      • functools.partial(f.read, numBytes) 应该也可以代替lambda
      • 标记应该是一个空字节串,b''。字符串文字是 Python 3 中的 Unicode 对象或 Python 2 中的 from __future__ import unicode_literals
      【解决方案4】:

      我不知道有任何内置方法可以做到这一点,但是包装函数很容易编写:

      def read_in_chunks(infile, chunk_size=1024*64):
          while True:
              chunk = infile.read(chunk_size)
              if chunk:
                  yield chunk
              else:
                  # The chunk was empty, which means we're at the end
                  # of the file
                  return
      

      然后在交互式提示处:

      >>> from chunks import read_in_chunks
      >>> infile = open('quicklisp.lisp')
      >>> for chunk in read_in_chunks(infile):
      ...     print chunk
      ... 
      <contents of quicklisp.lisp in chunks>
      

      当然,您可以轻松地将其调整为使用 with 块:

      with open('quicklisp.lisp') as infile:
          for chunk in read_in_chunks(infile):
              print chunk
      

      你可以像这样去掉 if 语句。

      def read_in_chunks(infile, chunk_size=1024*64):
          chunk = infile.read(chunk_size)
          while chunk:
              yield chunk
              chunk = infile.read(chunk_size)
      

      【讨论】:

      • 我以为有一些我只是忽略了的内置方式。由于似乎没有内置方式,因此易于阅读且直截了当。谢谢!
      猜你喜欢
      • 1970-01-01
      • 2016-03-27
      • 2019-12-20
      • 2016-07-21
      • 2020-12-23
      • 1970-01-01
      • 2020-09-25
      • 2012-05-26
      • 1970-01-01
      相关资源
      最近更新 更多