Python文件迭代器在二进制文件中使用较新的习惯用法

daw*_*awg 24 python iterator file

在Python中,对于二进制文件,我可以这样写:

buf_size=1024*64           # this is an important size...
with open(file, "rb") as f:
   while True:
      data=f.read(buf_size)
      if not data: break
      # deal with the data....
Run Code Online (Sandbox Code Playgroud)

有了我想逐行阅读的文本文件,我可以这样写:

with open(file, "r") as file:
   for line in file:
       # deal with each line....
Run Code Online (Sandbox Code Playgroud)

这是简写​​:

with open(file, "r") as file:
   for line in iter(file.readline, ""):
       # deal with each line....
Run Code Online (Sandbox Code Playgroud)

这个成语记录在PEP 234中,但我找不到二进制文件的类似习惯用法.

我试过这个:

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

>>> i
1                # 30 MB file, i==1 means read in one go...
Run Code Online (Sandbox Code Playgroud)

我试过put iter(f.read(buf_size),'')但是这是一个语法错误,因为在iter()中可调用后的parens.

我知道我可以编写一个函数,但是有for chunk in file:哪些方法可以使用默认的习惯用法,我可以使用缓冲区大小而不是面向行?

感谢您忍受Python新手试图编写他的第一个非平凡和惯用的Python脚本.

lio*_*ori 34

尝试:

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

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))
Run Code Online (Sandbox Code Playgroud)

def somefunction(): return f.read(how_many_bytes_you_want_each_time)
Run Code Online (Sandbox Code Playgroud)

在你的代码之前你可以写一下这些:iter(somefunction, '').

从技术上讲,你可以跳过lambda周围的括号,python的语法将接受它.

  • `functools.partial(f.read,numBytes)`也应该代替`lambda` (14认同)
  • sentinel应该是一个空字节串,'b''.字符串文字是Python 3中的Unicode对象,或者是Python 2中的"from __future__ import unicode_literals". (4认同)

Jas*_*ker 22

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

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
Run Code Online (Sandbox Code Playgroud)

然后在交互式提示符下:

>>> 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>
Run Code Online (Sandbox Code Playgroud)

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

with open('quicklisp.lisp') as infile:
    for chunk in read_in_chunks(infile):
        print chunk
Run Code Online (Sandbox Code Playgroud)

你可以像这样消除if语句.

def read_in_chunks(infile, chunk_size=1024*64):
    chunk = infile.read(chunk_size)
    while chunk:
        yield chunk
        chunk = infile.read(chunk_size)
Run Code Online (Sandbox Code Playgroud)


daw*_*awg 8

这个问题问了近 10 年之后,现在 Python 3.8 有了PEP 572中描述的:= Walrus Operator

要以惯用且富有表现力的方式读取块文件(使用 Python 3.8 或更高版本),您可以执行以下操作:

while chunk := file.read(1024 * 64):
    process(chunk)
Run Code Online (Sandbox Code Playgroud)


Mag*_*ero 7

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

iter(对象[,哨兵])

返回一个迭代器对象。根据第二个参数的存在,第一个参数的解释非常不同。如果没有第二个参数,object必须是支持迭代协议(__iter__()方法)的集合对象,或者它必须支持序列协议(__getitem__()从 开始的整数参数的方法0)。如果它不支持这些协议中的任何一个,TypeError则引发。如果给出了第二个参数sentinel,则object必须是可调用对象。在这种情况下创建的迭代器将在每次调用其方法时调用不带参数的对象__next__();如果返回的值等于哨兵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)
Run Code Online (Sandbox Code Playgroud)