读取二进制文件并循环每个字节
在Python中,如何读取二进制文件并循环该文件的每个字节?
f = open("myfile", "rb") try: byte = f.read(1) while byte != "": # Do stuff with byte. byte = f.read(1) finally: f.close()
通过chrispy的build议:
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中,它有点不同。 我们将不再从字节模式的字节对象中获取原始字符,因此我们需要改变条件:
with open("myfile", "rb") as f: byte = f.read(1) while byte != b"": # Do stuff with byte. byte = f.read(1)
或者如本霍伊特所说,跳过不平等,并利用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)
该生成器从文件中产生字节,以块为单位读取文件:
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)
有关迭代器和生成器的信息,请参阅Python文档。
如果文件不是太大,把它放在内存中是一个问题:
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()
要读取一个文件 – 一次一个字节(忽略缓冲) – 可以使用双参数iter(callable, sentinel)
内置函数 :
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自动closures文件 – 包括下面的代码引发exception的情况。
尽pipe默认存在内部缓冲,但一次处理一个字节的效率仍然很低。 例如,以下是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
时,它处理chunksize == 32768
GB / s ,当chunksize == 1
时只处理chunksize == 1
MB / s 。 也就是说,一次读取一个字节的速度要慢200倍。 考虑到这一点,如果你可以重写你的处理,一次使用多个字节,并且你需要性能。
mmap
允许你把一个文件同时作为一个bytearray
和一个文件对象。 如果你需要访问这两个接口,它可以作为加载整个文件的替代scheme。 特别是,你可以在一个内存映射文件中一次迭代一个字节,只需要使用一个普通的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之前不支持上下文pipe理器协议; 你需要在这种情况下明确地调用mm.close()
。 迭代使用mmap
每个字节比file.read(1)
消耗更多的内存,但是mmap
的速度要快一个数量级。
总结一下chrispy,Skurmedel,Ben Hoyt和Peter Hansen的所有优秀点,这将是一次处理一个二进制文件的最佳解决scheme:
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在内部缓冲 – 不需要读取块
- 干燥原则 – 不要重复读取线
- 与声明确保一个干净的文件closures
- 当没有更多字节时(不是字节为零时),“字节”计算为假
或者使用JF Sebastians解决scheme来提高速度
from functools import partial with open(filename, 'rb') as file: for byte in iter(partial(file.read, 1), b''): # Do stuff with byte
或者如果你想要它作为一个发电机function,如codeape演示:
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)
在Python中读取二进制文件并遍历每个字节
我们来创build一个函数来做到这一点:
def file_byte_iterator(path): """given a path, return an iterator over the file that lazily loads the file """ with open(path, 'rb') as file: for chunk in file: for byte in chunk: yield byte
例:
让我们来创build一个文件:
>>> path = '/temp/foobarbaz' >>> with open(path, 'w') as f: ... f.write('foo\nbar\nbaz')
现在让我们使用rb
标志(读取模式,字节模式)进行迭代。
请注意,多个for循环不会增加复杂性(仍然是O(n)) – 这就是您如何懒惰地逐行遍历文件的方式。
这将循环在代码中的每个字节,没有任何hacky .read(1)
业务。
>>> for byte in file_byte_iterator(path): ... print(byte) ... #print(ord(byte)) # Python 2 102 111 111 10 98 97 114 10 98 97 122
这是比我在其他答案中看到的while循环和复杂性更加Pythonic和自然。
缓冲读书
如果你有没有换行符的大文件,你可能想缓冲你的阅读。 Python 2.7需要io.open
才能得到这个:
>>> import io >>> with io.open('foobarbaz', 'rb') as f: ... for line in f: ... for byte in line: ... print(ord(byte))
我们现在有一个缓冲的阅读器:
>>> f <_io.BufferedReader name='foobarbaz'>
Python 3的内置open
函数是2的io.open
。
如果你有很多二进制数据要读取,你可能要考虑结构模块 。 它被logging为“在C和Pythontypes之间转换”,但是当然,字节是字节,并且它们是否被创build为Ctypes并不重要。 例如,如果你的二进制数据包含两个2字节整数和一个4字节整数,你可以按如下方式读取它们(例子来自struct
文档):
>>> struct.unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03') (1, 2, 3)
您可能会发现这比使用文件内容显式循环更方便,更快速,或者两者兼而有之。
Python 3,立刻读取所有的文件:
with open("filename", "rb") as binary_file: # Read the whole file at once data = binary_file.read() print(data)
你可以使用data
variables迭代任何你想要的。
我的解决scheme实际上返回一个字符
>>> from StringIO import StringIO >>> from functools import partial # credit to JF Sebastian >>> fl = StringIO('string\x00string2\x00') # simulated "file" object >>> bytearray(iter(partial(fl.read, 1), b'\x00')).__str__() # read until terminated 'string' >>> bytearray(iter(partial(fl.read, 1), b'\x00')).__str__() # again 'string2'
唯一的问题是如果你正在pipe理一个外部偏移量,你需要明确地加1来弥补终止字符。
编辑:
通常虽然我会做一些像array('B',[ord(c) for c in file.read()])
,并从那里手动进行数据pipe理。
(这是处理文件数据的最快方法,file.read(1)慢了很多倍)