gzip模块 — Gzip压缩

Python标准库精讲专题 · 压缩与归档篇 · 掌握Gzip压缩

专题:Python标准库精讲系统学习

关键词:Python, 标准库, gzip, 压缩, 解压, gz, compress, decompress, GzipFile

一、gzip模块概述

gzip是Python标准库中用于读写Gzip格式压缩文件的模块,基于GNU zip协议和DEFLATE压缩算法实现。它提供了与内置open()高度一致的接口,使得对.gz文件的读写操作几乎与普通文件无异,极大地降低了开发者使用文件压缩功能的学习成本。

核心定位:gzip模块是Python文件I/O体系中的重要一环,解决了"透明压缩"的问题——让开发者能以操作普通文件的方式处理gz压缩文件。

Gzip格式的特点

Gzip(GNU zip)由Jean-loup Gailly和Mark Adler在1992年创建,最初用于替代Unix系统上的compress工具。其核心特点包括:

与ZIP和TAR的关系

初学者经常混淆gzip、ZIP和TAR三种格式,理解它们的关系至关重要:

在Python中,gzip模块处理.gz文件,zipfile模块处理.zip文件,tarfile模块则可以处理.tar.tar.gz.tar.bz2等多种归档格式。

模块设计原则

Python的gzip模块在设计上遵循"最小意外原则",尽可能模拟内置文件对象的行为。这意味着:

gzip模块的设计理念是"你不需要知道文件是否被压缩"——无论是读还是写,接口都和普通文件几乎一样。

二、函数式API

gzip模块提供了三个高层的便利函数,覆盖了最常见的压缩和解压场景,无需实例化任何类即可直接使用。

gzip.open() — 透明压缩文件读写

gzip.open(filename, mode='rb', compresslevel=9, encoding=None, ...)是模块中使用频率最高的函数,其签名和行为都与内置open()高度相似。

# 写入压缩文件(二进制模式) import gzip # 写入:将文本压缩后写入.gz文件 with gzip.open('example.txt.gz', 'wt', encoding='utf-8') as f: f.write('Hello, Gzip World!\n') f.write('这是一个测试文本。\n') # 读取:自动解压后读取文本 with gzip.open('example.txt.gz', 'rt', encoding='utf-8') as f: content = f.read() print(content)

mode参数与前缀对应关系:

mode含义说明
'r' 或 'rb'二进制读模式默认模式,返回bytes数据
'rt'文本读模式结合encoding使用,返回str
'w' 或 'wb'二进制写模式写入bytes数据
'wt'文本写模式结合encoding使用,写入str
'x' 或 'xb'排他创建模式文件已存在时抛出FileExistsError
'a' 或 'ab'追加模式注意:追加到已有的.gz文件较特殊

重要提示:gzip格式本身不合适追加写入(追加后整个文件需重新解压才能读取新数据)。Python的gzip.open(mode='a')采用了兼容性方案——将新压缩数据追加到原始压缩流的末尾,但并非所有第三方解压工具都支持这种追加后的文件。

gzip.compress() — 字节数据快速压缩

gzip.compress(data, compresslevel=9)将输入的bytes数据压缩后返回新的bytes对象。适合处理内存中小数据块的压缩场景。

import gzip data = b'This is a test string to be compressed. ' * 100 # 压缩 compressed = gzip.compress(data) print(f'原始大小: {len(data)} 字节') print(f'压缩后大小: {len(compressed)} 字节') print(f'压缩比: {len(compressed) / len(data) * 100:.1f}%') # 解压 decompressed = gzip.decompress(compressed) print(f'解压后数据是否一致: {decompressed == data}')

gzip.decompress() — 字节数据解压

gzip.decompress(data)接收已压缩的bytes数据,返回解压后的原始bytes对象。与compress()成对使用,是处理内存压缩数据的最佳方式。

适用场景:compress()decompress()特别适合这样的场景——数据量适中(可全部放入内存)、不需要持久化为文件、希望代码最简。典型的例子包括数据库中的压缩字段存储、网络消息体压缩、缓存数据压缩等。

三、GzipFile类

gzip.GzipFile是模块的底层类实现,所有的函数式API(opencompressdecompress)在底层都依赖于GzipFile。直接使用GzipFile可以获得更精细的控制能力。

构造函数

GzipFile(filename=None, mode=None, compresslevel=9, fileobj=None, mtime=None)接受以下关键参数:

fileobj参数的威力

fileobj参数是GzipFile最强大的特性——它可以接受任何实现了文件接口(.read().write()等)的对象,包括io.BytesIOio.StringIO、网络Socket对象等。这意味着gzip压缩可以发生在内存中、网络上,而不仅限于磁盘文件。

# 在内存中压缩和解压数据 import gzip import io # 使用BytesIO在内存中完成gzip压缩 buf = io.BytesIO() with gzip.GzipFile(fileobj=buf, mode='wb') as f: f.write(b'Hello from in-memory compression!') # 获取压缩后的字节 compressed_bytes = buf.getvalue() print(f'内存压缩完成,大小: {len(compressed_bytes)} 字节') # 从内存中解压 buf_in = io.BytesIO(compressed_bytes) with gzip.GzipFile(fileobj=buf_in, mode='rb') as f: decompressed = f.read() print(f'解压后内容: {decompressed.decode()}')

文件对象式操作

GzipFile实例实现了完整的文件对象接口,包括:

注意:seek()方法对于gzip文件是"昂贵"的操作——因为gzip是流式压缩格式,要定位到解压后的某个位置,必须从数据流的起始位置开始重新解压直到目标位置。这与随机访问文件(如ZIP中每个条目独立压缩)有本质区别。

与gzip.open()的关系

gzip.open()在Python 3.x中是GzipFile的便捷包装器。两者之间的主要区别在于:

四、压缩级别

gzip模块支持0~9共10个压缩级别(实际上级别0表示无压缩),默认值为9(最高压缩比)。理解不同压缩级别的特性,有助于在实际项目中做出正确的权衡决策。

级别定义

级别含义典型压缩比相对速度
0不压缩(仅存储)100%最快
1最快压缩约40~70%比9快约5~10倍
2~3快速区域约35~65%速度适中
4~5平衡区域约30~60%较好的平衡点
6常用平衡点约28~55%许多工具(如gzip命令)的默认值
7~8高压缩区域约25~50%增益递减明显
9最高压缩比约22~48%最慢,额外增益约1~5%

不同级别对比示例

import gzip import time data = b'A' * 50000 data += b'Random: ' + bytes(range(200)) * 200 for level in [1, 3, 6, 9]: start = time.time() compressed = gzip.compress(data, compresslevel=level) elapsed = time.time() - start ratio = len(compressed) / len(data) * 100 print(f'级别 {level}: {len(compressed):6d} 字节 ({ratio:.1f}%) | {elapsed*1000:.2f} ms')

如何选择压缩级别

在实际应用中选择压缩级别时,应考虑以下因素:

经验法则:对于文本文件(日志、JSON、CSV、源代码等),gzip通常能达到70~90%的压缩率,效果非常显著。对于已压缩格式(图片、视频、压缩包),gzip几乎不会有额外收益。选择压缩级别时,级别1和级别9之间的压缩比差异通常在5~15%之间,但速度差异可达5~10倍,理解这个trade-off至关重要。

五、实战应用

以下三个实战场景展示了gzip模块在不同环境下的典型用法,涵盖了从文件处理、网络传输到系统管理的全面需求。

场景一:大文件流式压缩

处理大文件时,不能一次性将整个文件读入内存。流式压缩通过分块读写,使内存占用保持恒定。配合shutil.copyfileobj()可以大幅简化代码。

import gzip import shutil import os def compress_file(src_path, dst_path=None, chunk_size=64 * 1024): """流式压缩大文件,避免内存暴涨""" if dst_path is None: dst_path = src_path + '.gz' with open(src_path, 'rb') as src: with gzip.open(dst_path, 'wb') as dst: # 分块读取源文件并压缩写入,内存占用恒定为 chunk_size shutil.copyfileobj(src, dst, length=chunk_size) orig_size = os.path.getsize(src_path) comp_size = os.path.getsize(dst_path) ratio = comp_size / orig_size * 100 print(f'{src_path}: {orig_size} -> {comp_size} 字节 ({ratio:.1f}%)') def decompress_file(gz_path, dst_path=None): """流式解压.gz文件""" if dst_path is None: dst_path = gz_path[:-3] # 去掉 .gz 后缀 with gzip.open(gz_path, 'rb') as src: with open(dst_path, 'wb') as dst: shutil.copyfileobj(src, dst) # 使用示例 compress_file('large_log.txt') decompress_file('large_log.txt.gz')

使用shutil.copyfileobj()的核心优势在于:不需要显式编写循环读取-写入的代码,由copyfileobj内部自动分块处理,同时gzip的流式特性确保了压缩和解压过程都是增量进行的。

场景二:HTTP内容压缩

在Web应用和API服务中,gzip压缩是减少网络传输量的标准方案。大部分HTTP客户端和服务端库都内置了gzip支持,但了解底层实现有助于调试和性能优化。

# 使用请求库时启用gzip(requests库默认自动解压) import requests response = requests.get('https://httpbin.org/gzip') # requests 内部自动处理了 gzip 解压 print(response.text[:200]) # 手动构建gzip压缩的HTTP响应体 import gzip import json data = {'message': 'This is a large JSON response', 'items': list(range(1000))} body = json.dumps(data).encode('utf-8') compressed_body = gzip.compress(body) print(f'原始大小: {len(body)} 字节') print(f'压缩后大小: {len(compressed_body)} 字节') # 设置正确的HTTP响应头 headers = { 'Content-Encoding': 'gzip', 'Content-Type': 'application/json', 'Content-Length': str(len(compressed_body)), }

Web性能建议:在Nginx或CDN层面启用gzip压缩通常比在应用代码中手动压缩更高效。这些中间件通常使用级别1~6,并内置了针对不同内容类型(如文本启用、图片禁用)的智能策略。当需要压缩动态生成的响应时,也可以考虑在WSGI服务器(如Gunicorn)层面统一配置。

场景三:日志压缩归档

日志文件是gzip压缩的高频应用场景。通过结合Python的logging模块和gzip,可以实现日志的自动轮转和压缩归档,大幅节省磁盘空间。

# 自定义日志处理器——轮转时自动gzip压缩 import gzip import os import logging from logging.handlers import RotatingFileHandler class GzipRotatingFileHandler(RotatingFileHandler): """日志轮转后自动gzip压缩旧文件""" def rotation_filename(self, default_name): """轮转后的文件名添加 .gz 后缀""" return default_name + '.gz' def rotate(self, source, dest): """将轮转出的日志文件压缩为gz格式""" with open(source, 'rb') as src: with gzip.open(dest, 'wb') as dst: dst.write(src.read()) os.remove(source) # 删除原始未压缩文件 # 配置使用 handler = GzipRotatingFileHandler( 'app.log', maxBytes=10 * 1024 * 1024, backupCount=5 ) handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')) logger = logging.getLogger('my_app') logger.addHandler(handler) logger.setLevel(logging.DEBUG) # 测试:写大量日志触发轮转 for i in range(100000): logger.info(f'这是一条测试日志消息 #{i}')

这个自定义处理器继承自RotatingFileHandler,在日志轮转发生时将旧文件自动压缩为.log.gz格式,同时删除原始未压缩文件。实际项目中可以将此模式推广到TimedRotatingFileHandler,实现按时间(每日/每周)轮转并在轮转后压缩归档。

与zipfile的配合使用

虽然gzipzipfile是不同的模块,但在某些场景中需要配合使用。例如,读取.zip包中的gz压缩数据:

import zipfile import gzip import io # 从zip包中读取gz文件并解压 with zipfile.ZipFile('archive.zip', 'r') as zf: # 假设包内有一个 data.csv.gz raw_gz = zf.read('data.csv.gz') # 解压gz内容 decompressed = gzip.decompress(raw_gz) print(decompressed.decode('utf-8')[:500])

六、核心总结

gzip模块核心要点速览

  • 一句话定位:Python标准库中用于读写Gzip格式压缩文件的模块,接口模仿内置open(),实现"透明压缩"
  • 最常用函数gzip.open() — 像操作普通文件一样操作.gz文件;gzip.compress()/gzip.decompress() — 内存中的字节数据快速压缩/解压
  • 底层类GzipFile — 支持fileobj参数,可在内存流、网络流等任意类文件对象上实现gzip压缩
  • 压缩级别:1~9级,默认9。级别1压缩速度最快、但压缩比最低;级别9压缩比最高、但速度最慢。CPU敏感场景考虑使用1~6
  • 流式处理:gzip天然支持流式操作,配合shutil.copyfileobj()可高效处理超大文件
  • Web场景:HTTP的Content-Encoding: gzip是传输压缩的标准方式;requests/httpx等库默认支持透明解压

与其他模块的协作关系

模块关系说明
shutil配合shutil.copyfileobj()实现大文件的流式压缩和解压
tarfile读取/创建.tar.gz文件,内部使用gzip算法
zipfile处理.zip格式(基于DEFLATE算法,与gzip同源但不兼容)
io配合io.BytesIO在内存中完成gzip压缩,无需磁盘I/O
bz2类似功能的模块,使用BZ2算法(压缩比更高但速度更慢)
lzma类似功能的模块,使用LZMA算法(高压缩比,速度较慢)
logging自定义日志处理器实现日志轮转后的自动gzip压缩归档

最佳实践清单

总结:gzip模块是Python标准库中"小而美"的典范——接口简单、功能聚焦、与语言内建I/O体系无缝融合。理解它不仅能让你高效处理.gz文件,更能为你理解整个Python数据压缩生态(bz2、lzma、zipfile、tarfile)打下坚实基础。