Python读取二进制文件:模式、字节处理与分块读取详解
本文详解Python中读取二进制文件的方法。通过open函数的rb模式获取字节对象,解释其与字符串的区别。介绍使用ZipFile等库处理特定格式,并深入讲解利用hashlib模块计算SHA-256校验和。针对大文件,演示如何分块读取以节省内存,并展示Python 3.8引入的海象运算符在循环读取中的应用。
Python读取二进制文件的基本方法
在Python中读取文本文件的操作广为人知,但当面对二进制文件时,直接沿用默认读取方式往往会遇到障碍。例如,尝试使用Python内置的open函数以默认模式打开zip文件时,程序会抛出错误信息,提示无法正确解码内容。
>>> with open("exercises.zip") as zip_file:
... contents = zip_file.read()
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "/usr/lib/python3.10/codecs.py", line 322, in de
code
(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8e in position 11: invalid sta
rt byte出现上述错误的原因在于,zip文件并非纯文本格式,而是由二进制数据构成的文件。因此,必须采用适用于二进制数据的读取策略,否则无法正确解析文件内容。
要成功读取二进制文件,关键在于使用open函数时指定正确的模式。与默认的文本读取模式(rt)不同,必须显式使用“rb”模式来打开文件,这样才能确保以二进制方式读取数据。
>>> with open("exercises.zip", mode="rb") as zip_file:
... contents = zip_file.read()当通过“rb”模式从二进制文件中读取内容时,返回的结果不再是常规的字符串对象,而是一个字节对象(bytes)。这种数据类型专门用于存储原始二进制数据。
>>> with open("exercises.zip", mode="rb") as zip_file:
... contents = zip_file.read()
...
>>> type(contents)
<class 'bytes'>
>>> contents[:20]
b'PK\x03\x04\n\x00\x00\x00\x00\x00Y\x8e\x84T\x00\x00\x00\x00\x00\x00'字节对象内部不包含人类可读的字符,而是由一系列字节组成。这意味着直接查看字节内容通常无法直观理解其含义,需要特定的解码或解析逻辑。
如果无法正确解读这些字节的含义,那么文件中的原始字节数据对用户而言几乎没有实际价值。因此,理解字节结构与对应文件格式至关重要。
使用库处理二进制文件
为了更高效地处理二进制文件,通常建议借助专门的库函数。
在处理二进制数据时,最佳实践是依赖针对特定文件类型设计的库,无论是Python内置库还是第三方库。这些库内部封装了复杂的字节解码逻辑,能将原始字节转换为更易用的数据结构。
以Python的ZipFile模块为例,它可以专门用于读取zip文件中的数据。通过该模块,可以直接提取文件内容并进行解码,从而避免手动处理复杂的二进制结构。
>>> from zipfile import ZipFile
>>>
>>> with ZipFile("exercises.zip") as zip_file:
... test_file = zip_file.read("exercises/test.py").decode("utf-8")
...
>>> test_file[:30]
'#!/usr/bin/env python3\nfrom __'既然已有成熟的库解决了二进制文件的解析问题,开发者应优先使用这些库,尽量避免自行实现字节检查或复杂的字节操作逻辑,以减少出错风险。
在Python中以字节级别工作
尽管有库可用,但在某些场景下仍需直接在字节层面进行操作。
有时,开发者会使用或被要求直接使用在字节级别工作的库或API。在这种情况下,必须对二进制文件的基本概念以及字节字符串的特性有清晰的认识,才能正确操作数据。
一个典型的例子是计算文件的SHA-256校验和,这通常需要在字节层面读取文件内容。
为此,可以定义一个名为get_sha256_hash的函数来执行计算任务。
import hashlib
def get_sha256_hash(filename):
with open(filename, mode="rb") as f:
return hashlib.sha256(f.read()).hexdigest()该函数的核心逻辑是读取文件中的所有二进制数据。由于Python的hashlib模块底层仅接受字节输入,因此必须使用“rb”模式读取字节,而非字符串。
函数将文件的所有字节传入以生成哈希对象,随后调用该对象的hexdigest方法,最终返回表示文件SHA-256校验和的十六进制字符串。
>>> get_sha256_hash("exercises.zip")
'9e98242a21760945ec815668fc79d8621fa15dd23659ea29be2c5949153fe96d'上述功能在小文件上运行良好,但当处理体积庞大的文件时,这种一次性读取所有数据的方法会引发内存问题。
分块读取二进制文件
为了解决大文件读取导致的内存占用问题,需要采用分块读取策略。
前文的get_sha256_hash函数会将整个文件一次性加载到内存中。对于非常大的文件,这可能导致内存耗尽或系统性能下降。
处理文本文件时,逐行读取是常见的优化手段。然而,二进制文件通常不包含明确的行分隔符,因此不能直接套用逐行读取的方法,而应采用逐块读取的策略。
首先,尝试从文件中读取一个固定大小的数据块,例如8 KB。
import hashlib
def get_sha256_hash(filename, buffer_size=2**10*8):
file_hash = hashlib.sha256()
with open(filename, mode="rb") as f:
chunk = f.read(buffer_size)具体实现时,先创建一个新的哈希对象,然后通过调用文件对象的read方法并指定字节数(如8 KB)来读取第一个数据块。
为了获取文件的剩余部分,需要引入循环结构来持续读取数据。
import hashlib
def get_sha256_hash(filename, buffer_size=2**10*8):
file_hash = hashlib.sha256()
with open(filename, mode="rb") as f:
chunk = f.read(buffer_size)
while chunk:
file_hash.update(chunk)
chunk = f.read(buffer_size)
return file_hash.hexdigest()在循环中,重复执行读取一个数据块的操作,将读取到的字节更新到哈希对象中,然后继续读取下一个数据块。
只要读取操作未到达文件末尾,read方法就会返回包含数据的字节块。
然而,当读取操作到达文件末尾时,read方法将返回一个空的字节字符串。类似于空字符串,空字节字符串在布尔上下文中被视为False,因此可以利用这一点在文件末尾跳出循环,最后像之前一样返回十六进制摘要。
>>> get_sha256_hash("exercises.zip")
'9e98242a21760945ec815668fc79d8621fa15dd23659ea29be2c5949153fe96d'通过这种方式,我们不再将完整文件加载到内存,而是以分块的方式逐步读取文件,从而显著降低内存占用。
使用赋值表达式简化代码
在实现分块读取时,可以使用Python的赋值表达式来简化代码。
在逐块读取文件的场景中,经常可以看到使用赋值表达式(即Python的海象运算符 :=)的代码写法。
import hashlib
def get_sha256_hash(filename, buffer_size=2**10*8):
file_hash = hashlib.sha256()
with open(filename, mode="rb") as f:
while chunk := f.read(buffer_size):
file_hash.update(chunk)
return file_hash.hexdigest()在while循环中重复读取数据是赋值表达式的典型应用场景。虽然这种写法初看可能略显陌生,但它能有效减少代码行数,使逻辑更加紧凑。
注意:海象运算符是在Python 3.8版本中引入的新特性。
总结而言,在Python中读取二进制文件时,返回的是字节对象。对于大型二进制文件,应当采用分块读取策略以优化内存使用。此外,如果存在合适的第三方库,优先使用库函数处理二进制文件是更稳妥的选择。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









