用 Python 压缩文件方法汇总
对于流行的文件压缩格式,如 tar
、zip
、gzip
、bz2
等,乃至于更奇特的 lzma
等格式,Python 都能轻易实现。本文将对有关压缩文件的问题给予阐述。
压缩格式以及相关模块
Python 提供了几乎为所有现有压缩文件的工具,下面逐一领略。
-
zlib
是一个 Python 库,能够实现zip
、gzip
格式文件的压缩和解压缩。 -
bz2
模块提供了对bzip2
格式的压缩支持。它也只对单个文件起作用,因此不能归档。 -
lzma
既是算法的名称,也是 Python 模块。它可以产生比一些旧方法更高的压缩比,并且是xz
(更具体地说是 LZMA2 )背后的算法。 -
gzip
是大多数人都熟悉的应用,此外它也是一个 Python 模块的名称。此模块使用前面提到的zlib
压缩算法,并充当类似于实用程序gzip
和gunzip
的接口。 -
shutils
是一个模块,我们通常不把该模块与压缩和解压缩联系在一起。但它提供了处理归档文件的实用方法,便于生成tar
、gztar
、zip
、bztar
或者xztar
这些类型的归档文件。 -
顾名思义,
zipfile
允许我们用 Python 中实现 zip 归档,提供了创建、读取、写入或追加zip
文件所需的所有方法,还提供了便于操作这些文件的类和对象。 -
和上面的
zipfile
类似,tarfile
这个模块用于实现tar
归档,可以读取和写入gzip
、bz2
和lzma
文件或归档文件。也支持与常规的tar
压缩软件能实现的其他功能。
压缩与解压缩
上面列出了很多选择,它们中有一些比较基本,有一些具有许多其他功能,但共同点显然是包含压缩功能。下面就来看看有关基本操作。
先看 zlib
,这是一个相当低级的库,因此可能不太常用,让我们来看看针对整个文件的压缩或解压缩方法。
import zlib, sys
filename_in = "data"
filename_out = "compressed_data"
with open(filename_in, mode="rb") as fin, open(filename_out, mode="wb") as fout:
data = fin.read()
compressed_data = zlib.compress(data, zlib.Z_BEST_COMPRESSION)
print(f"Original size: {sys.getsizeof(data)}")
# Original size: 1000033
print(f"Compressed size: {sys.getsizeof(compressed_data)}")
# Compressed size: 1024
fout.write(compressed_data)
with open(filename_out, mode="rb") as fin:
data = fin.read()
compressed_data = zlib.decompress(data)
print(f"Compressed size: {sys.getsizeof(data)}")
# Compressed size: 1024
print(f"Decompressed size: {sys.getsizeof(compressed_data)}")
# Decompressed size: 1000033
上面的代码中所需要的输入文件,可以用 head -c 1MB </dev/zero > data
指令生成,此文件由零组成且大小为 1MB 。将文件读入内存滞后,用 zlib
中的 compress
方法创建压缩数据。然后将该数据写入输出文件。
为了证明能够恢复数据——解压缩,再次打开上述生成的压缩文件并对其通过 zlibb
的 decompress
方法。通过 print
,可以看到压缩和解压缩数据的大小都是匹配的。
下一个是 bz2
,它的使用方式与上面的 zlib
非常相似:
import bz2, os, sys
filename_in = "data"
filename_out = "compressed_data.bz2"
with open(filename_in, mode="rb") as fin, bz2.open(filename_out, "wb") as fout:
fout.write(fin.read())
print(f"Uncompressed size: {os.stat(filename_in).st_size}")
# Uncompressed size: 1000000
print(f"Compressed size: {os.stat(filename_out).st_size}")
# Compressed size: 48
with bz2.open(filename_out, "rb") as fin:
data = fin.read()
print(f"Decompressed size: {sys.getsizeof(data)}")
# Decompressed size: 1000033
不出所料,使用方法大同小异。为了显示一些不同之处,在上面的示例中,我们简化了压缩步骤,将其减少到1行,并使用 os.stat
来检查文件的大小。
这些低级模块中的最后一个是 lzma
,为了避免反复显示相同的代码,这次执行增量压缩:
import lzma, os
lzc = lzma.LZMACompressor()
# cat /usr/share/dict/words | sort -R | head -c 1MB > data
filename_in = "data"
filename_out = "compressed_data.xz"
with open(filename_in, mode="r") as fin, open(filename_out, "wb") as fout:
for chunk in fin.read(1024):
compressed_chunk = lzc.compress(chunk.encode("ascii"))
fout.write(compressed_chunk)
fout.write(lzc.flush())
print(f"Uncompressed size: {os.stat(filename_in).st_size}")
# Uncompressed size: 972398
print(f"Compressed size: {os.stat(filename_out).st_size}")
# Compressed size: 736
with lzma.open(filename_out, "r") as fin:
words = fin.read().decode("utf-8").split()
print(words[:5])
# ['dabbing', 'hauled', "seediness's", 'Iroquoian', 'vibe']
首先创建一个输入文件,文件中包含从字典中提取的一组单词,该字典在 /usr/share/dict/words
中,这样可以确认解压后的数据与原始数据相同。
然后,我们像前面的示例一样打开输入和输出文件。然而,这一次在 1024 位块中迭代随机数据,并使用 LZMACompressor.compress
方法压缩它们。然后将这些块写入输出文件。在读取和压缩整个文件之后,我们需要调用 flush
,以完成压缩过程、并从压缩器中清除任何剩余数据。
为了证实上述操作的有效性,我们以通常的方式打开并解压缩文件,并从文件中打印出几个单词。
下面要研究高级别的模块。现在使用 gzip
执行相同的任务:
import os, sys, shutil, gzip
filename_in = "data"
filename_out = "compressed_data.tar.gz"
with open(filename_in, "rb") as fin, gzip.open(filename_out, "wb") as fout:
# Reads the file by chunks to avoid exhausting memory
shutil.copyfileobj(fin, fout)
print(f"Uncompressed size: {os.stat(filename_in).st_size}")
# Uncompressed size: 1000000
print(f"Compressed size: {os.stat(filename_out).st_size}")
# Compressed size: 1023
with gzip.open(filename_out, "rb") as fin:
data = fin.read()
print(f"Decompressed size: {sys.getsizeof(data)}")
# Decompressed size: 1000033
在这个例子中,结合了 gzip
和 shutils
。看起来我们所做的批量压缩与之前使用 zlib
或 bz2
的效果相同,但由于 shutil.copyfileobj
方法,我们实现了分块增量压缩,而不必像使用lzma
那样循环数据。
gzip
模块的一个优点是:它还提供了命令行接口,我说的不是 Linux gzip
和 gunzip
,而是 Python 中所集成的:
python3 -m gzip -h
usage: gzip.py [-h] [--fast | --best | -d] [file [file ...]]
...
ls -l data*
-rw-rw-r-- 1 martin martin 1000000 aug 22 18:48 data
# Use fast compression on file "data"
python3 -m gzip --fast data
# File named "data.gz" was generated:
ls -l data*
-rw-rw-r-- 1 martin martin 1000000 aug 22 18:48 data
-rw-rw-r-- 1 martin martin 1008 aug 22 20:50 data.gz
更高效的工具
如果你熟悉 zip
或 tar
,或者必须用其中的一种格式存档,就应该认真阅读下面的内容。除了基本的压缩或解压缩操作外,这两个模块还包括其他的一些实用方法,例如校验、使用密码、在归档文件中列出文件等。所以,很有必要深入研究一番,确保掌握这些技能。
import zipfile
# shuf -n5 /usr/share/dict/words > words.txt
files = ["words1.txt", "words2.txt", "words3.txt", "words4.txt", "words5.txt"]
archive = "archive.zip"
password = b"verysecret"
with zipfile.ZipFile(archive, "w") as zf:
for file in files:
zf.write(file)
zf.setpassword(password)
with zipfile.ZipFile(archive, "r") as zf:
crc_test = zf.testzip()
if crc_test is not None:
print(f"Bad CRC or file headers: {crc_test}")
info = zf.infolist() # also zf.namelist()
print(info)
# See all attributes at https://docs.python.org/3/library/zipfile.html#zipinfo-objects
# [ <ZipInfo filename='words1.txt' filemode='-rw-r--r--' file_size=37>,
# <ZipInfo filename='words2.txt' filemode='-rw-r--r--' file_size=47>,
# ... ]
file = info[0]
with zf.open(file) as f:
print(f.read().decode())
# Olav
# teakettles
# ...
zf.extract(file, "/tmp", pwd=password) # also zf.extractall()
上述代码有点长,它涵盖了 zipfile
模块的所有重要功能。在这段代码中,首先在 with
上下文管理中,以 w
模式使用 ZipFile
创建 ZIP 归档文件,然后将文件添加到归档文件中。你会注意到,实际上不需要打开要添加的文件 —— 我们所需要做的就是调用 write
方法,并传入文件名为参数。添加所有文件后,我们还使用 setpassword
方法设置存档密码。
接下来,为了证明这种操作方法的有效性,打开归档文件。在读取任何文件之前,检查CRC和文件头,然后检索存档中所有文件的信息。在本例中,我们只打印 ZipInfo
对象的列表,但你也可以检查其属性,以获得CRC、大小、压缩类型等。
检查完所有文件后,打开并读取其中一个文件。我们看到它具有预期的内容,所以可以继续并将其解压缩都指定路径(/tmp/
)。
除了创建和读取归档文件或普通文件外,ZIP 还允许我们将文件追加到现有的存档中。为此,只需将访问模式更改为 a
(追加模式):
with zipfile.ZipFile(archive, "a") as zf:
zf.write("words6.txt")
print(zf.namelist())
# ['words1.txt', 'words2.txt', 'words3.txt', 'words4.txt', 'words5.txt', 'words6.txt']
与 gzip
模块相同,Python的 zipfile
和 tarfile
也提供 CLI 。要执行基本存档和提取,请使用以下命令:
python3 -m zipfile -c arch.zip words1.txt words2.txt # Create
python3 -m zipfile -t arch.zip # Test
Done testing
python3 -m zipfile -e arch.zip /tmp # Extract
ls /tmp/words*
/tmp/words1.txt /tmp/words2.txt
最后但并非最不重要的是 tarfile
模块。此模块类似于 zipfile
,但也实现了一些额外的功能:
import tarfile
files = ["words1.txt", "words2.txt", "words3.txt", "words4.txt"]
archive = "archive.tar.gz"
with tarfile.open(archive, "w:gz") as tar:
for file in files:
tar.add(file) # can also be dir (added recursively), symlink, etc
print(f"archive contains: {tar.getmembers()}")
# [<TarInfo 'words1.txt' at 0x7f71ed74f8e0>,
# <TarInfo 'words2.txt' at 0x7f71ed74f9a8>
# ... ]
info = tar.gettarinfo("words1.txt") # Other Linux attributes - https://docs.python.org/3/library/tarfile.html#tarinfo-objects
print(f"{tar.name} contains {info.name} with permissions {oct(info.mode)[-3:]}, size: {info.size} and owner: {info.uid}:{info.gid}")
# .../archive.tar contains words1.txt with permissions 644, size: 37 and owner: 500:500
def change_permissions(tarinfo):
tarinfo.mode = 0o100600 # -rw-------.
return tarinfo
tar.add("words5.txt", filter=change_permissions)
tar.list()
# -rw-r--r-- martin/martin 37 2021-08-23 09:01:56 words1.txt
# -rw-r--r-- martin/martin 47 2021-08-23 09:02:06 words2.txt
# ...
# -rw------- martin/martin 42 2021-08-23 09:02:22 words5.txt
我们从归档文件的基本创建开始,这里使用的打开模式 "w:gz"
,指定要使用 GZ 压缩。然后将所有的文件添加到存档中。使用 tarfile
模块,还可以传入符号链接(软连接)、或传入可以递归添加的整个目录。
接下来,为了确认所有文件都确实存在,我们使用 getmembers
方法。为了深入了解各个文件,可以使用 gettarinfo
方法,它提供了所有 Linux 文件属性。
tarfile
提供了一个我们在其他模块中没有看到的很酷的特性,那就是在将文件添加到归档文件时能够修改文件的属性。在上面的代码片段中,通过提供 filter
参数来更改文件的权限,该参数修改了 TarInfo.mode
。此值必须作为八进制数提供,此处的 0o100600
将权限设置为 0600
或 -rw-------.
。
为了在进行此更改后获得文件的完整概览,我们可以运行 list
方法,它提供类似于 ls -l
的输出。
使用tar
存档的最后一件事是打开它并将其解压缩。为此,我们使用 "r:gz"
模式打开它,以文件名作为 getmember
方法的参数,返回文件对象,并将其解压缩到指定路径中。
with tarfile.open(archive, "r:gz") as tar:
member = tar.getmember("words3.txt")
if member.isfile():
tar.extract(member, "/tmp/")
结论
如你所见,Python 的提供了包括低级和高级、特定和通用、简单和复杂的各类模块或库。可以根据实际需要进行选择,通常建议使用通用模块,如 zipfile
或 tarfile
,只有在必要时才使用 lzma
之类的模块。
当然,要想熟练使用以上各个模块的各种方法,还是要阅读官方文档。
参考文献
https://towardsdatascience.com/all-the-ways-to-compress-and-archive-files-in-python-e8076ccedb4b