13.5. "zipfile" --- 使用ZIP存档
*******************************

**源代码：** Lib/zipfile.py

======================================================================

ZIP 文件格式是一个常用的归档与压缩标准。 这个模块提供了创建、读取、写
入、添加及列出 ZIP 文件的工具。 任何对此模块的进阶使用都将需要理解此格
式，其定义参见 PKZIP 应用程序笔记。

此模块目前不能处理分卷 ZIP 文件。它可以处理使用 ZIP64 扩展（超过 4 GB
的 ZIP 文件）的 ZIP 文件。它支持解密 ZIP 归档中的加密文件，但是目前不
能创建一个加密的文件。解密非常慢，因为它是使用原生 Python 而不是 C 实
现的。

这个模块定义了以下内容：

exception zipfile.BadZipFile

   为损坏的 ZIP 文件抛出的错误。

   3.2 新版功能.

exception zipfile.BadZipfile

   "BadZipFile" 的别名，与旧版本 Python 保持兼容性。

   3.2 版后已移除.

exception zipfile.LargeZipFile

   当 ZIP 文件需要 ZIP64 功能但是未启用时会抛出此错误。

class zipfile.ZipFile

   用于读写 ZIP 文件的类。 欲了解构造函数的描述，参阅段落 ZipFile 对象
   。

class zipfile.PyZipFile

   用于创建包含 Python 库的 ZIP 归档的类。

class zipfile.ZipInfo(filename='NoName', date_time=(1980, 1, 1, 0, 0, 0))

   用于表示档案内一个成员信息的类。 此类的实例会由 "ZipFile" 对象的
   "getinfo()" 和 "infolist()" 方法返回。 大多数 "zipfile" 模块的用户
   都不必创建它们，只需使用此模块所创建的实例。 *filename* 应当是档案
   成员的全名，*date_time* 应当是包含六个字段的描述最近修改时间的元组
   ；这些字段的描述请参阅 ZipInfo 对象。

zipfile.is_zipfile(filename)

   根据文件的 Magic Number，如果 *filename* 是一个有效的 ZIP 文件则返
   回 "True"，否则返回 "False"。 *filename* 也可能是一个文件或类文件对
   象。

   在 3.1 版更改: 支持文件或类文件对象。

zipfile.ZIP_STORED

   未被压缩的归档成员的数字常数。

zipfile.ZIP_DEFLATED

   常用的 ZIP 压缩方法的数字常数。需要 "zlib" 模块。

zipfile.ZIP_BZIP2

   BZIP2 压缩方法的数字常数。需要 "bz2" 模块。

   3.3 新版功能.

zipfile.ZIP_LZMA

   LZMA 压缩方法的数字常数。需要 "lzma" 模块。

   3.3 新版功能.

   注解:

     ZIP 文件格式规范包括自 2001 年以来对 bzip2 压缩的支持，以及自
     2006 年以来对 LZMA 压缩的支持。但是，一些工具（包括较旧的 Python
     版本）不支持这些压缩方法，并且可能拒绝完全处理 ZIP 文件，或者无法
     提取单个文件。

参见:

  PKZIP 应用程序笔记
     Phil Katz 编写的 ZIP 文件格式文档，此格式和使用的算法的创建者。

  Info-ZIP 主页
     有关 Info-ZIP 项目的 ZIP 存档程序和开发库的信息。


13.5.1. ZipFile 对象
====================

class zipfile.ZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True)

   Open a ZIP file, where *file* can be a path to a file (a string), a
   file-like object or a *path-like object*. The *mode* parameter
   should be "'r'" to read an existing file, "'w'" to truncate and
   write a new file, "'a'" to append to an existing file, or "'x'" to
   exclusively create and write a new file. If *mode* is "'x'" and
   *file* refers to an existing file, a "FileExistsError" will be
   raised. If *mode* is "'a'" and *file* refers to an existing ZIP
   file, then additional files are added to it.  If *file* does not
   refer to a ZIP file, then a new ZIP archive is appended to the
   file.  This is meant for adding a ZIP archive to another file (such
   as "python.exe").  If *mode* is "'a'" and the file does not exist
   at all, it is created. If *mode* is "'r'" or "'a'", the file should
   be seekable. *compression* is the ZIP compression method to use
   when writing the archive, and should be "ZIP_STORED",
   "ZIP_DEFLATED", "ZIP_BZIP2" or "ZIP_LZMA"; unrecognized values will
   cause "NotImplementedError" to be raised.  If "ZIP_DEFLATED",
   "ZIP_BZIP2" or "ZIP_LZMA" is specified but the corresponding module
   ("zlib", "bz2" or "lzma") is not available, "RuntimeError" is
   raised. The default is "ZIP_STORED".  If *allowZip64* is "True"
   (the default) zipfile will create ZIP files that use the ZIP64
   extensions when the zipfile is larger than 4 GiB. If it is  false
   "zipfile" will raise an exception when the ZIP file would require
   ZIP64 extensions.

   如果创建文件时使用 "'w'", "'x'" 或 "'a'" 模式并且未向归档添加任何文
   件就执行了 "closed"，则会将适当的空归档 ZIP 结构写入文件。

   ZipFile is also a context manager and therefore supports the "with"
   statement.  In the example, *myzip* is closed after the "with"
   statement's suite is finished---even if an exception occurs:

      with ZipFile('spam.zip', 'w') as myzip:
          myzip.write('eggs.txt')

   3.2 新版功能: 添加了将 "ZipFile" 用作上下文管理员的功能。

   在 3.3 版更改: 添加了对 "bzip2" 和 "lzma" 压缩的支持。

   在 3.4 版更改: 默认启用 ZIP64 扩展。

   在 3.5 版更改: 添加了对不可查找数据流的支持。 并添加了对 "'x'" 模式
   的支持。

   在 3.6 版更改: 在此之前，对于不可识别的压缩值将引发普通的
   "RuntimeError"。

   在 3.6.2 版更改: *file* 形参接受一个 *path-like object*。

ZipFile.close()

   关闭归档文件。 你必须在退出程序之前调用 "close()" 否则将不会写入关
   键记录数据。

ZipFile.getinfo(name)

   返回一个 "ZipInfo" 对象，其中包含有关归档成员 *name* 的信息。 针对
   一个目前并不包含于归档中的名称调用 "getinfo()" 将会引发 "KeyError"
   。

ZipFile.infolist()

   返回一个列表，其中包含每个归档成员的 "ZipInfo" 对象。 如果是打开一
   个现有归档则这些对象的排列顺序与它们对应条目在磁盘上的实际 ZIP 文件
   中的顺序一致。

ZipFile.namelist()

   返回按名称排序的归档成员列表。

ZipFile.open(name, mode='r', pwd=None, *, force_zip64=False)

   以二进制文件类对象的形式访问一个归档成员。 *name* 可以是归档内某个
   文件的名称也可以是某个 "ZipInfo" 对象。 如果包含了 *mode* 形参，则
   它必须为 "'r'" (默认值) 或 "'w'"。  *pwd* 为用于解密已加密 ZIP 文件
   的密码。

   "open()" 也是一个上下文管理器，因此支持 "with" 语句:

      with ZipFile('spam.zip') as myzip:
          with myzip.open('eggs.txt') as myfile:
              print(myfile.read())

   With *mode* "'r'" the file-like object ("ZipExtFile") is read-only
   and provides the following methods: "read()", "readline()",
   "readlines()", "__iter__()", "__next__()".  These objects can
   operate independently of the ZipFile.

   如果 "mode='w'" 则返回一个可写入的文件句柄，它将支持 "write()" 方法
   。 当一个可写入的文件句柄被打开时，尝试读写 ZIP 文件中的其他文件将
   会引发 "ValueError"。

   当写入一个文件时，如果文件大小不能预先确定但是可能超过 2 GiB，可传
   入 "force_zip64=True" 以确保标头格式能够支持超大文件。 如果文件大小
   可以预先确定，则在构造 "ZipInfo" 对象时应设置 "file_size"，并将其用
   作 *name* 形参。

   注解:

     "open()", "read()" 和 "extract()" 方法可接受文件名或 "ZipInfo" 对
     象。 当尝试读取一个包含重复名称成员的 ZIP 文件时你将发现此功能很
     有好处。

   在 3.6 版更改: 移除了对 "mode='U'" 的支持。 请使用
   "io.TextIOWrapper" 以在 *universal newlines* 模式中读取已压缩的文本
   文件。

   在 3.6 版更改: "open()" 现在可以被用来配合 "mode='w'" 选项来将文件
   写入归档。

   在 3.6 版更改: 在已关闭的 ZipFile 上调用 "open()" 将引发
   "ValueError"。 在之前的版本中则会引发 "RuntimeError"。

ZipFile.extract(member, path=None, pwd=None)

   从归档中提取出一个成员放入当前工作目录；*member* 必须为成员的完整名
   称或 "ZipInfo" 对象。 成员的文件信息会尽可能精确地被提取。 *path*
   指定一个要提取到的不同目录。 *member* 可以是一个文件名或 "ZipInfo"
   对象。 *pwd* 是用于解密文件的密码。

   返回所创建的经正规化的路径（对应于目录或新文件）。

   注解:

     如果一个成员文件名为绝对路径，则将去掉驱动器/UNC共享点和前导的（
     反）斜杠，例如: "///foo/bar" 在 Unix 上将变为 "foo/bar"，而
     "C:\foo\bar" 在 Windows 上将变为 "foo\bar"。 并且一个成员文件名中
     的所有 "".."" 都将被移除，例如: "../../foo../../ba..r" 将变为
     "foo../ba..r"。 在 Windows 上非法字符 (":", "<", ">", "|", """,
     "?", and "*") 会被替换为下划线 ("_")。

   在 3.6 版更改: 在已关闭的 ZipFile 上调用 "extract()" 将引发
   "ValueError"。 在之前的版本中则将引发 "RuntimeError"。

   在 3.6.2 版更改: *path* 形参接受一个 *path-like object*。

ZipFile.extractall(path=None, members=None, pwd=None)

   从归档中提取出所有成员放入当前工作目录。 *path* 指定一个要提取到的
   不同目录。 *members* 为可选项且必须为 "namelist()" 所返回列表的一个
   子集。 *pwd* 是用于解密文件的密码。

   警告:

     绝不要未经预先检验就从不可靠的源中提取归档文件。 这样有可能在
     *path* 之外创建文件，例如某些成员具有以 ""/"" 开始的文件名或带有
     两个点号 "".."" 的文件名。 此模块会尝试防止这种情况。 参见
     "extract()" 的注释。

   在 3.6 版更改: 在已关闭的 ZipFile 上调用 "extractall()" 将引发
   "ValueError"。 在之前的版本中则将引发 "RuntimeError"。

   在 3.6.2 版更改: *path* 形参接受一个 *path-like object*。

ZipFile.printdir()

   将归档的目录表打印到 "sys.stdout"。

ZipFile.setpassword(pwd)

   设置 *pwd* 为用于提取已加密文件的默认密码。

ZipFile.read(name, pwd=None)

   返回归档中文件 *name* 的字节数据。 *name* 是归档中文件的名称，或是
   一个 "ZipInfo" 对象。 归档必须以读取或追加方式打开。 *pwd* 为用于已
   加密文件的密码，并且如果指定该参数则它将覆盖通过 "setpassword()" 设
   置的默认密码。  on a ZipFile that uses a compression method 在使用
   "ZIP_STORED" , "ZIP_DEFLATED", "ZIP_BZIP2" 或 "ZIP_LZMA" 以外的压缩
   方法的 ZipFile 上调用 "read()" 将引发 "NotImplementedError"。 如果
   相应的压缩模块不可用也会引发错误。

   在 3.6 版更改: 在已关闭的 ZipFile 上调用 "read()" 将引发
   "ValueError"。 在之前的版本中则会引发 "RuntimeError".

ZipFile.testzip()

   读取归档中的所有文件并检查它们的 CRC 和文件头。 返回第一个已损坏文
   件的名称，在其他情况下则返回 "None"。

   在 3.6 版更改: 在已关闭的 ZipFile 上调用 "testzip()" 将引发
   "ValueError"。 在之前的版本中则将引发 "RuntimeError"。

ZipFile.write(filename, arcname=None, compress_type=None)

   Write the file named *filename* to the archive, giving it the
   archive name *arcname* (by default, this will be the same as
   *filename*, but without a drive letter and with leading path
   separators removed).  If given, *compress_type* overrides the value
   given for the *compression* parameter to the constructor for the
   new entry. The archive must be open with mode "'w'", "'x'" or
   "'a'".

   注解:

     归档名称应当是基于归档根目录的相对路径，也就是说，它们不应以路径
     分隔符开头。

   注解:

     如果 "arcname" (或 "filename"，如果 "arcname" 未给出) 包含一个空
     字节，则归档中该文件的名称将在空字节位置被截断。

   在 3.6 版更改: 在使用 "'r'" 模式创建的 ZipFile 或已关闭的 ZipFile
   上调用 "write()" 将引发 "ValueError"。 在之前的版本中则会引发
   "RuntimeError"。

ZipFile.writestr(zinfo_or_arcname, data[, compress_type])

   将一个文件写入归档。 内容为 *data*，它可以是一个 "str" 或 "bytes"
   的实例；如果是 "str"，则会先使用 UTF-8 进行编码。
   *zinfo_or_arcname* 可以是它在归档中将被给予的名称，或者是 "ZipInfo"
   的实例。 如果它是一个实例，则至少必须给定文件名、日期和时间。 如果
   它是一个名称，则日期和时间会被设为当前日期和时间。 归档必须以
   "'w'", "'x'" 或 "'a'" 模式打开。

   If given, *compress_type* overrides the value given for the
   *compression* parameter to the constructor for the new entry, or in
   the *zinfo_or_arcname* (if that is a "ZipInfo" instance).

   注解:

     当传入一个 "ZipInfo" 实例作为 *zinfo_or_arcname* 形参时，所使用的
     压缩方法将为在给定的 "ZipInfo" 实例的 *compress_type* 成员中指定
     的方法。 默认情况下，"ZipInfo" 构造器将将此成员设为 "ZIP_STORED"
     。

   在 3.2 版更改: *compress_type* 参数。

   在 3.6 版更改: 在使用 "'r'" 模式创建的 ZipFile 或已关闭的 ZipFile
   上调用 "writestr()" 将引发 "ValueError"。 在之前的版本中则会引发
   "RuntimeError"。

以下数据属性也是可用的:

ZipFile.filename

   ZIP 文件的名称。

ZipFile.debug

   要使用的调试输出等级。 这可以设为从 "0" (默认无输出) 到 "3" (最多输
   出) 的值。 调试信息会被写入 "sys.stdout"。

ZipFile.comment

   关联到 ZIP 文件的 "bytes" 对象形式的说明。 如果将说明赋给以 "'w'",
   "'x'" 或 "'a'" 模式创建的 "ZipFile" 实例，它的长度不应超过 65535 字
   节。 超过此长度的说明将被截断。


13.5.2. PyZipFile 对象
======================

"PyZipFile" 构造器接受与 "ZipFile" 构造器相同的形参，以及一个额外的形
参 *optimize*。

class zipfile.PyZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True, optimize=-1)

   3.2 新版功能: *optimize* 形参。

   在 3.4 版更改: 默认启用 ZIP64 扩展。

   实例在 "ZipFile" 对象所具有的方法以外还附加了一个方法:

   writepy(pathname, basename='', filterfunc=None)

      查找 "*.py" 文件并将相应的文件添加到归档。

      如果 "PyZipFile" 的 *optimize* 形参未给定或为 "-1"，则相应的文件
      为 "*.pyc" 文件，并在必要时进行编译。

      如果 "PyZipFile" 的 *optimize* 形参为 "0", "1" 或 "2"，则限具有
      相应优化级别 (参见 "compile()") 的文件会被添加到归档，并在必要时
      进行编译。

      If *pathname* is a file, the filename must end with ".py", and
      just the (corresponding "*.pyc") file is added at the top level
      (no path information).  If *pathname* is a file that does not
      end with ".py", a "RuntimeError" will be raised.  If it is a
      directory, and the directory is not a package directory, then
      all the files "*.pyc" are added at the top level.  If the
      directory is a package directory, then all "*.pyc" are added
      under the package name as a file path, and if any subdirectories
      are package directories, all of these are added recursively.

      *basename* 仅限在内部使用。

      如果给定 *filterfunc*，则它必须是一个接受单个字符串参数的函数。
      在将其添加到归档之前它将被传入每个路径（包括每个单独的完整路径）
      。 如果 *filterfunc* 返回假值，则路径将不会被添加，而如果它是一
      个目录则其内容将被忽略。 例如，如果我们的测试文件全都位于 "test"
      目录或以字符串 "test_" 打头，则我们可以使用一个 *filterfunc* 来
      排除它们:

         >>> zf = PyZipFile('myprog.zip')
         >>> def notests(s):
         ...     fn = os.path.basename(s)
         ...     return (not (fn == 'test' or fn.startswith('test_')))
         >>> zf.writepy('myprog', filterfunc=notests)

      "writepy()" 方法会产生带有这样一些文件名的归档:

         string.pyc                   # Top level name
         test/__init__.pyc            # Package directory
         test/testall.pyc             # Module test.testall
         test/bogus/__init__.pyc      # Subpackage directory
         test/bogus/myfile.pyc        # Submodule test.bogus.myfile

      3.4 新版功能: *filterfunc* 形参。

      在 3.6.2 版更改: *pathname* 形参接受一个 *path-like object*。


13.5.3. ZipInfo 对象
====================

"ZipInfo" 类的实例会通过 "getinfo()" 和 "ZipFile" 对象的 "infolist()"
方法返回。 每个对象将存储关于 ZIP 归档的一个成员的信息。

有一个类方法可以为文件系统文件创建 "ZipInfo" 实例:

classmethod ZipInfo.from_file(filename, arcname=None)

   为文件系统中的文件构造一个 "ZipInfo" 实例，并准备将其添加到一个 zip
   文件。

   *filename* 应为文件系统中某个文件或目录的路径。

   如果指定了 *arcname*，它会被用作归档中的名称。 如果未指定 *arcname*
   ，则所用名称与 *filename* 相同，但将去除任何驱动器盘符和打头的路径
   分隔符。

   3.6 新版功能.

   在 3.6.2 版更改: *filename* 形参接受一个 *path-like object*。

实例具有下列方法和属性:

ZipInfo.is_dir()

   如果此归档成员是一个目录则返回 "True"。

   这会使用条目的名称：目录应当总是以 "/" 结尾。

   3.6 新版功能.

ZipInfo.filename

   归档中的文件名称。

ZipInfo.date_time

   上次修改存档成员的时间和日期。这是六个值的元组：

   +---------+----------------------------+
   | 索引    | 值                         |
   |=========|============================|
   | "0"     | Year (>= 1980)             |
   +---------+----------------------------+
   | "1"     | 月（1为基数）              |
   +---------+----------------------------+
   | "2"     | 月份中的日期（1为基数）    |
   +---------+----------------------------+
   | "3"     | 小时（0为基数）            |
   +---------+----------------------------+
   | "4"     | 分钟（0为基数）            |
   +---------+----------------------------+
   | "5"     | 秒（0为基数）              |
   +---------+----------------------------+

   注解:

     ZIP文件格式不支持1980年以前的时间戳。

ZipInfo.compress_type

   归档成员的压缩类型。

ZipInfo.comment

   "bytes" 对象形式的单个归档成员的注释。

ZipInfo.extra

   扩展字段数据。 PKZIP Application Note 包含一些保存于该 "bytes" 对象
   中的内部结构的注释。

ZipInfo.create_system

   创建 ZIP 归档所用的系统。

ZipInfo.create_version

   创建 ZIP 归档所用的 PKZIP 版本。

ZipInfo.extract_version

   需要用来提取归档的 PKZIP 版本。

ZipInfo.reserved

   必须为零。

ZipInfo.flag_bits

   ZIP 标志位。

ZipInfo.volume

   文件中的分卷号。

ZipInfo.internal_attr

   内部属性。

ZipInfo.external_attr

   外部文件属性。

ZipInfo.header_offset

   文件头的字节偏移量。

ZipInfo.CRC

   未压缩文件的 CRC-32。

ZipInfo.compress_size

   已压缩数据的大小。

ZipInfo.file_size

   未压缩文件的大小。


13.5.4. 命令行界面
==================

"zipfile" 模块提供了简单的命令行接口用于与 ZIP 归档的交互。

如果你想要创建一个新的 ZIP 归档，请在 "-c" 选项后指定其名称然后列出应
当被包含的文件名:

   $ python -m zipfile -c monty.zip spam.txt eggs.txt

传入一个字典也是可接受的:

   $ python -m zipfile -c monty.zip life-of-brian_1979/

如果你想要将一个 ZIP 归档提取到指定的目录，请使用 "-e" 选项:

   $ python -m zipfile -e monty.zip target-dir/

对于一个 ZIP 归档中的文件列表，请使用 "-l" 选项:

   $ python -m zipfile -l monty.zip


13.5.4.1. 命令行选项
--------------------

-l <zipfile>

   列出一个 zipfile 中的文件名。

-c <zipfile> <source1> ... <sourceN>

   基于源文件创建 zipfile。

-e <zipfile> <output_dir>

   将 zipfile 提取到目标目录中。

-t <zipfile>

   检测 zipfile 是否有效。
