第三节:二进制文件操作——图片、音频等二进制文件的读写
聊完了文本文件,咱们来碰碰真正的硬骨头——二进制文件。
你想想看,一张图片、一段音频、一个视频,它们本质上是什么?是一串0和1。文本文件好歹还有个编码规则(UTF-8、GBK),二进制文件可不管这些,它只认字节。
我个人习惯把二进制文件比作「原始食材」。文本文件是切好、配好料的半成品,而二进制文件就是刚从地里拔出来的萝卜——你得直接上手处理。
3.1 为什么需要二进制模式?
Python 默认用文本模式打开文件。但处理图片、音频时,必须用 rb(读二进制)或 wb(写二进制)。
为什么?因为文本模式会做「翻译」工作。比如 Windows 下,\n 会被翻译成 \r\n。对于文本文件这没问题,但对于图片——你想想,图片里某个字节恰好是 0x0A(也就是 \n),被翻译后整个文件就坏了。
rb / wb。
3.2 核心操作:rb 和 wb
说白了,二进制读写就两件事:读进来是 bytes,写出去也是 bytes。
3.2.1 读取二进制文件
with open('photo.jpg', 'rb') as f:
data = f.read()
print(type(data)) # <class 'bytes'>
print(len(data)) # 文件字节数
看到没?data 是 bytes 类型,不是字符串。你不能对它做 .encode() 或 .decode()(除非你知道编码规则)。
3.2.2 写入二进制文件
with open('copy.jpg', 'wb') as f:
f.write(data)
就这么简单。把读出来的字节原封不动写进去,就是文件复制。
3.3 实战:图片复制工具
咱们写个完整的例子。把一张图片复制成新文件,同时打印一些元信息。
def copy_image(src_path, dst_path):
try:
with open(src_path, 'rb') as src:
data = src.read()
with open(dst_path, 'wb') as dst:
dst.write(data)
print(f'✅ 复制成功!')
print(f' 源文件: {src_path}')
print(f' 目标文件: {dst_path}')
print(f' 文件大小: {len(data)} 字节')
except FileNotFoundError:
print(f'❌ 源文件不存在: {src_path}')
except PermissionError:
print(f'❌ 没有权限访问文件')
except Exception as e:
print(f'❌ 发生未知错误: {e}')
# 使用示例
copy_image('input.png', 'output.png')
嗯,这里要注意:try-except 不是摆设。我在项目中遇到过用户给了个不存在的路径,程序直接崩溃。加个异常处理,用户体验好很多。
3.4 逐块读写:处理大文件
如果文件很大(比如几百 MB 的视频),一次性 f.read() 会吃掉大量内存。怎么办?逐块读写。
def copy_large_file(src_path, dst_path, chunk_size=1024*1024):
"""逐块复制大文件,默认每块 1MB"""
with open(src_path, 'rb') as src, open(dst_path, 'wb') as dst:
while True:
chunk = src.read(chunk_size)
if not chunk:
break
dst.write(chunk)
print(f'✅ 大文件复制完成')
# 复制一个视频文件
copy_large_file('video.mp4', 'video_backup.mp4')
为什么用 while True?因为 read(chunk_size) 读到文件末尾会返回空字节 b'',这就是退出条件。
3.5 二进制文件的定位:seek 和 tell
有时候你不想从头读起。比如一个音频文件,你想跳过前 44 字节的 WAV 文件头,直接读音频数据。
with open('audio.wav', 'rb') as f:
# 查看当前位置
print(f.tell()) # 0,文件开头
# 跳过文件头(WAV 头通常 44 字节)
f.seek(44)
print(f.tell()) # 44
# 读取音频数据
audio_data = f.read(1024)
# 回到开头
f.seek(0)
print(f.tell()) # 0
seek(offset, whence) 的第二个参数:
| whence 值 | 含义 | 示例 |
|---|---|---|
| 0 | 文件开头(默认) | f.seek(100, 0) |
| 1 | 当前位置 | f.seek(50, 1) |
| 2 | 文件末尾 | f.seek(-10, 2) |
seek 的行为很诡异(因为编码问题)。二进制模式下,seek 和 tell 才可靠。我建议:需要定位操作时,一律用二进制模式。
3.6 实战:读取图片的二进制头部信息
每张图片都有文件头,里面藏着格式信息。比如 PNG 的前 8 个字节是固定签名。
def check_png_signature(file_path):
"""检查文件是否为 PNG 格式"""
png_signature = b'\x89PNG\r\n\x1a\n'
with open(file_path, 'rb') as f:
header = f.read(8)
if header == png_signature:
print('✅ 这是一个 PNG 文件')
return True
else:
print(f'❌ 不是 PNG 文件,头部为: {header.hex()}')
return False
# 测试
check_png_signature('test.png')
你想想看,这种技巧有什么用?我在做文件上传系统时,就用它来校验用户上传的是不是真正的图片,而不是伪装成图片的恶意文件。
3.7 二进制文件操作总结
来,咱们捋一下核心要点:
- 模式选择:非文本文件一律用
rb/wb - 数据类型:读写都是
bytes,不是str - 大文件处理:用
read(chunk_size)逐块读写 - 定位操作:
seek和tell在二进制模式下最可靠 - 异常处理:文件操作必须加
try-except