9.6. zipfile — 访问 ZIP 压缩文件
目的:读取和写入 ZIP 存档文件。
zipfile
模块可用于操作 ZIP 存档文件,.zip
是 PC 程序 PKZIP 推广的格式。
为了演示用,你需要创建以下三个文件
- README.txt 内容如下,注意最后一行为空行
The examples for the zipfile module use this file and example.zip as data.
- 使用压缩软件将 README.txt 压缩为 example.zip,压缩式选储存选项,不需要压缩
- 新建一个空的文本文档,并改名为 bad_example.zip
测试 ZIP 文件
is_zipfile()
函数返回一个布尔值,指示作为参数传递的文件名是否指向有效的 ZIP 存档。
zipfile_is_zipfile.py
import zipfile
for filename in ['README.txt', 'example.zip',
'bad_example.zip', 'notthere.zip']:
print('{:>15} {}'.format(
filename, zipfile.is_zipfile(filename)))
如果文件根本不存在,is_zipfile()
返回 False
。
$ python3 zipfile_is_zipfile.py
README.txt False
example.zip True
bad_example.zip False
notthere.zip False
注意:由于
zipfile_is_zipfile.py
中使用的是相对路径,你需要在同一个目录/路径下放置测试文件以及 Python 脚本,并在这个路径下使用 Python 解释器执行脚本,才能得到正确的输出。否则你需要在 Python 脚本中使用绝对路径。下述所有的例子均需要放置在同一级目录下。
从 Zip 存档中读取元数据
使用 ZipFile
类直接处理 Zip 存档文件。它支持从现有存档中读取数据,也支持向现有存档中加入文件、修改存档。
zipfile_namelist.py
import zipfile
with zipfile.ZipFile('example.zip', 'r') as zf:
print(zf.namelist())
namelist()
方法以列表的形式返回已有存档中所有的文件名。
$ python3 zipfile_namelist.py
['README.txt']
存档中的文件名列表只是存档中可用信息的一小部分。可以使用 infolist()
或 getinfo()
方法访问 Zip 文件的全部元数据。
zipfile_infolist.py
import datetime
import zipfile
def print_info(archive_name):
with zipfile.ZipFile(archive_name) as zf:
for info in zf.infolist():
print(info.filename)
print(' Comment :', info.comment)
mod_date = datetime.datetime(*info.date_time)
print(' Modified :', mod_date)
if info.create_system == 0:
system = 'Windows'
elif info.create_system == 3:
system = 'Unix'
else:
system = 'UNKNOWN'
print(' System :', system)
print(' ZIP version :', info.create_version)
print(' Compressed :', info.compress_size, 'bytes')
print(' Uncompressed:', info.file_size, 'bytes')
print()
if __name__ == '__main__':
print_info('example.zip')
除了以上输出信息之外,Zip 存档还包含额外的数据。不过你需要仔细阅读 PKZIP 应用注释 中的 Zip 格式相关的部分,然后才能把这些附加数据解密为有用的信息。
$ python3 zipfile_infolist.py
README.txt
Comment : b''
Modified : 2010-11-15 06:48:02
System : Unix
ZIP version : 30
Compressed : 65 bytes
Uncompressed: 76 bytes
如果你已经预先知道了 Zip 存档中某个文件的名字。你可以通过 getinfo()
方法直接得到它的 ZipInfo
对象。
zipfile_getinfo.py
import zipfile
with zipfile.ZipFile('example.zip') as zf:
for filename in ['README.txt', 'notthere.txt']:
try:
info = zf.getinfo(filename)
except KeyError:
print('ERROR: Did not find {} in zip file'.format(
filename))
else:
print('{} is {} bytes'.format(
info.filename, info.file_size))
如果存档中的某个文件不存在,调用 getinfo()
方法会产生一个 KeyError
。
$ python3 zipfile_getinfo.py
README.txt is 76 bytes
ERROR: Did not find notthere.txt in zip file
从 Zip 存档中读取文件
要访问存档中的数据,可以将文件名传递给 read()
方法,返回结果为对应文件的内容。
zipfile_read.py
import zipfile
with zipfile.ZipFile('example.zip') as zf:
for filename in ['README.txt', 'notthere.txt']:
try:
data = zf.read(filename)
except KeyError:
print('ERROR: Did not find {} in zip file'.format(
filename))
else:
print(filename, ':')
print(data)
print()
如果有必要,数据会被自动解压。
$ python3 zipfile_read.py
README.txt :
b'The examples for the zipfile module use \nthis file and exampl
e.zip as data.\n'
ERROR: Did not find notthere.txt in zip file
创建新存档
要创建新的 Zip 存档,需要以 'w'
模式实例化一个 ZipFile
对象,存档中任何现有的文件都会被清空,相当于新建一个存档开始写入。如果你想要向现有存档中添加文件,你可以使用 write()
方法。
zipfile_write.py
from zipfile_infolist import print_info
import zipfile
print('creating archive')
with zipfile.ZipFile('write.zip', mode='w') as zf:
print('adding README.txt')
zf.write('README.txt')
print()
print_info('write.zip')
默认情况下,存档的文件不会被压缩。
$ python3 zipfile_write.py
creating archive
adding README.txt
README.txt
Comment : b''
Modified : 2016-08-07 13:31:24
System : Unix
ZIP version : 20
Compressed : 76 bytes
Uncompressed: 76 bytes
要实现压缩功能,需要使用 zlib
模块。如果 zlib
模块可用,你可以使用 zipfile.ZIP_DEFLATED
选项,让 zipfile
进入对单独的文件或整个存档进行压缩的模式。默认的压缩设置是 zipfile.ZIP_STORED
,这种模式下 zipfile
只会将文件添加进存档而不压缩它。
zipfile_write_compression.py
from zipfile_infolist import print_info
import zipfile
try:
import zlib
compression = zipfile.ZIP_DEFLATED
except (ImportError, AttributeError):
compression = zipfile.ZIP_STORED
modes = {
zipfile.ZIP_DEFLATED: 'deflated',
zipfile.ZIP_STORED: 'stored',
}
print('creating archive')
with zipfile.ZipFile('write_compression.zip', mode='w') as zf:
mode_name = modes[compression]
print('adding README.txt with compression mode', mode_name)
zf.write('README.txt', compress_type=compression)
print()
print_info('write_compression.zip')
此时,存档内容已被压缩了。
$ python3 zipfile_write_compression.py
creating archive
adding README.txt with compression mode deflated
README.txt
Comment : b''
Modified : 2016-08-07 13:31:24
System : Unix
ZIP version : 20
Compressed : 65 bytes
Uncompressed: 76 bytes
使用其它的存档成员名
给 write()
方法传递一个 arcname
值,你就可以将文件以新的文件名加入存档。等价于将文件改名后再加入存档。
zipfile_write_arcname.py
from zipfile_infolist import print_info
import zipfile
with zipfile.ZipFile('write_arcname.zip', mode='w') as zf:
zf.write('README.txt', arcname='NOT_README.txt')
print_info('write_arcname.zip')
如你所见,存档中的文本文件没有使用原来的文件名。
$ python3 zipfile_write_arcname.py
NOT_README.txt
Comment : b''
Modified : 2016-08-07 13:31:24
System : Unix
ZIP version : 20
Compressed : 76 bytes
Uncompressed: 76 bytes
从变量而不是文件写入数据
有时候你需要将某个字符串写入 Zip 存档,而不是将现有的文件加入存档。你不需要先把字符串写入文件,然后再将文件写入存档,通过 writestr()
方法,你可以直接将字节流的字符串写入 Zip 存档。
zipfile_writestr.py
from zipfile_infolist import print_info
import zipfile
msg = 'This data did not exist in a file.'
with zipfile.ZipFile('writestr.zip',
mode='w',
compression=zipfile.ZIP_DEFLATED,
) as zf:
zf.writestr('from_string.txt', msg)
print_info('writestr.zip')
with zipfile.ZipFile('writestr.zip', 'r') as zf:
print(zf.read('from_string.txt'))
以上示例中,传递给 ZipFile
的 compress_type
参数指定了需要压缩数据,因为 writestr()
方法不支持指定是否压缩的参数。
$ python3 zipfile_writestr.py
from_string.txt
Comment : b''
Modified : 2016-12-29 12:14:42
System : Unix
ZIP version : 20
Compressed : 36 bytes
Uncompressed: 34 bytes
b'This data did not exist in a file.'
通过 ZipInfo 实例写入
通常情况下,当你向存档加入新文件或写入字符串时,存档的修改日期会自动被计算并更新。你可以将 一个 ZipInfo
实例传递给 writestr()
方法,这样你就可以自定义存档的修改日期以及其它的元数据了。
zipfile_writestr_zipinfo.py
import time
import zipfile
from zipfile_infolist import print_info
msg = b'This data did not exist in a file.'
with zipfile.ZipFile('writestr_zipinfo.zip',
mode='w',
) as zf:
info = zipfile.ZipInfo('from_string.txt',
date_time=time.localtime(time.time()),
)
info.compress_type = zipfile.ZIP_DEFLATED
info.comment = b'Remarks go here'
info.create_system = 0
zf.writestr(info, msg)
print_info('writestr_zipinfo.zip')
在这个例子里,修改时间定为当前时间、数据被压缩了、使用了一个假的 create_system
值「原作者使用 Unix 系统,对应的 create_system
值为 3
,而这里设置为 0
对应 Windows 系统」并加上了一条简单的注释。
$ python3 zipfile_writestr_zipinfo.py
from_string.txt
Comment : b'Remarks go here'
Modified : 2016-12-29 12:14:42
System : Windows
ZIP version : 20
Compressed : 36 bytes
Uncompressed: 34 bytes
追加文件
除了创建新文件外,我们也可以向已有的存档中追加新文件,或者向现存的文件尾部追加新的存档,现有的文件可以是 .exe
文件或者自解压存档。要打开文件并在尾部追加内容,请使用 'a'
模式。
zipfile_append.py
from zipfile_infolist import print_info
import zipfile
print('creating archive')
with zipfile.ZipFile('append.zip', mode='w') as zf:
zf.write('README.txt')
print()
print_info('append.zip')
print('appending to the archive')
with zipfile.ZipFile('append.zip', mode='a') as zf:
zf.write('README.txt', arcname='README2.txt')
print()
print_info('append.zip')
输出的档案包含两个文件:
$ python3 zipfile_append.py
creating archive
README.txt
Comment : b''
Modified : 2016-08-07 13:31:24
System : Unix
ZIP version : 20
Compressed : 76 bytes
Uncompressed: 76 bytes
appending to the archive
README.txt
Comment : b''
Modified : 2016-08-07 13:31:24
System : Unix
ZIP version : 20
Compressed : 76 bytes
Uncompressed: 76 bytes
README2.txt
Comment : b''
Modified : 2016-08-07 13:31:24
System : Unix
ZIP version : 20
Compressed : 76 bytes
Uncompressed: 76 bytes
Python ZIP 存档
Python 可以通过 zipimport
从 sys.path
路径中的 Zip 存档内导入模块。我们可以将已经编写好的
PyZipFile
类构造成一个可以这样使用的模块。额外的 writepy()
方法告诉 PyZipFile
扫描当前目录下的每一个 .py
文件,并将对应的 .pyo
或 .pyc
文件添加进 Zip 存档。如果两种格式的文件都不存在,新的 .pyc
文件会被编译并加入存档中。
zipfile_pyzipfile.py
import sys
import zipfile
if __name__ == '__main__':
with zipfile.PyZipFile('pyzipfile.zip', mode='w') as zf:
zf.debug = 3
print('Adding python files')
zf.writepy('.')
for name in zf.namelist():
print(name)
print()
sys.path.insert(0, 'pyzipfile.zip')
import zipfile_pyzipfile
print('Imported from:', zipfile_pyzipfile.__file__)
当 PyZipFile
的调试属性被设为 3
时,编译每一个 .py
文件时都会出详细的调试信息。
$ python3 zipfile_pyzipfile.py
Adding python files
Adding files from directory .
Compiling ./zipfile_append.py
Adding zipfile_append.pyc
Compiling ./zipfile_getinfo.py
Adding zipfile_getinfo.pyc
Compiling ./zipfile_infolist.py
Adding zipfile_infolist.pyc
Compiling ./zipfile_is_zipfile.py
Adding zipfile_is_zipfile.pyc
Compiling ./zipfile_namelist.py
Adding zipfile_namelist.pyc
Compiling ./zipfile_printdir.py
Adding zipfile_printdir.pyc
Compiling ./zipfile_pyzipfile.py
Adding zipfile_pyzipfile.pyc
Compiling ./zipfile_read.py
Adding zipfile_read.pyc
Compiling ./zipfile_write.py
Adding zipfile_write.pyc
Compiling ./zipfile_write_arcname.py
Adding zipfile_write_arcname.pyc
Compiling ./zipfile_write_compression.py
Adding zipfile_write_compression.pyc
Compiling ./zipfile_writestr.py
Adding zipfile_writestr.pyc
Compiling ./zipfile_writestr_zipinfo.py
Adding zipfile_writestr_zipinfo.pyc
zipfile_append.pyc
zipfile_getinfo.pyc
zipfile_infolist.pyc
zipfile_is_zipfile.pyc
zipfile_namelist.pyc
zipfile_printdir.pyc
zipfile_pyzipfile.pyc
zipfile_read.pyc
zipfile_write.pyc
zipfile_write_arcname.pyc
zipfile_write_compression.pyc
zipfile_writestr.pyc
zipfile_writestr_zipinfo.pyc
Imported from: pyzipfile.zip/zipfile_pyzipfile.pyc
局限性
zipfile
模块不支持带附加注释的 ZIP 文件,也不支持分卷压缩文件。通过 ZIP64 扩展它可以支持大于 4GB 的 ZIP 文件。
参见
zipfile
的标准库文档zlib
-- ZIP 压缩库tarfile
-- 读写tai
存档zipimport
-- 从 ZIP 存档中导入 Python 库- PKZIP 应用注释 -- ZIP 存档文件格式的官方说明
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。