>>> from env_helper import info; info()
页面更新时间: 2023-06-30 19:19:44
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-9-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

3.7. 项目:将一个文件夹备份到一个 ZIP 文件

假定你正在做一个项目,它的文件保存在C::raw-latex:`\AlsPythonBook文件夹中`。 你担心工作会丢失,所以希望为整个文件夹创建一个ZIP文件,作为“快照”。 你希望保存不同的版本,希望 ZIP文件的文件名每次创建时都有所变化。 例如 AlsPythonBook_1.zip、AlsPythonBook_2.zip 、 AlsPythonBook_3.zip,等等。你可以手工完成,但这有点烦人, 而且可能不小心弄错 ZIP 文件的编号。 运行一个程序来完成这个烦人的任务会简单得多。

针对这个项目,打开一个新的文件编辑器窗口, 将它保存为 backupToZip.py

3.7.1. 第1步:弄清楚 ZIP 文件的名称

这个程序的代码将放在一个名为 backupToZip() 的 函数中。这样就更容易将该函数复制粘贴到其他需要 这个功能的Python程序中。在这个程序的末尾,会调用这个 函数进行备份。让你的程序看起来像这样:

#! python3
# backupToZip.py - Copies an entire folder and its contents into
# a ZIP file whose filename increments.

import zipfile, os

def backupToZip(folder):
    # Backup the entire contents of "folder" into a ZIP file.

    folder = os.path.abspath(folder) # make sure folder is absolute

    # Figure out the filename this code should use based on
    # what files already exist.
    number = 1
    while True:
        zipFilename = os.path.basename(folder) + '_' + str(number) + '.zip'
        if not os.path.exists(zipFilename):
            break
        number = number + 1

    # TODO: Create the ZIP file.

    # TODO: Walk the entire folder tree and compress the files in each folder.
    print('Done.')

backupToZip('F:\\delicious')

先完成基本任务:添加 #! 行,描述该程序做什么, 并导入 zipfileos 模块。

定义 backupToZip() 函数,它只接收一个参数, 即 folder 。这个参数是一个字符串路径, 指向需要备份的文件夹。该函数将决定它创建的 ZIP文件使用什么文件名,然后创建该文件, 遍历 folder 文件夹,将每个子文件夹和文件添加到ZIP文件中。 在源代码中为这些步骤写下 TODO 注释,提醒你稍后来完成。

第一部分命名这个ZIP文件,使用folder 的绝对路径的基本名称。 如果要备份的文件夹是 C::raw-latex:`delicious `, ZIP 文件的名称就应该 是 delicious_N.zip ,第一次运行该程 序时 N = l,第二次运行时 N =2,以此类推。

通过检查 delicious_1.zip 是否存在, 然后检查 delicious_2.zip 是否存在,继续下 去,可以确定 N 应该是什么。用一个名为 number 的变量表示 N, 在一个循环内不断增加它,并调用 os.path.exists() 来 检查该文件是否存在。第一个不存在的文件名将导致循环 break,因此它就发现了新 ZIP 文件的文件名。

3.7.2. 第2步:创建新 ZIP 文件

接下来让我们创建 ZIP 文件。让你的程序看起来像这样:

#! python3
# backupToZip.py - Copies an entire folder and its contents into
# a ZIP file whose filename increments.

--snip--
    while True:
        zipFilename = os.path.basename(folder) + '_' + str(number) + '.zip'
        if not os.path.exists(zipFilename):
            break
        number = number + 1

    # Create the ZIP file.
    print('Creating %s...' % (zipFilename))
    backupZip = zipfile.ZipFile(zipFilename, 'w')

    # TODO: Walk the entire folder tree and compress the files in each folder.
    print('Done.')

backupToZip('F:\\delicious')

既然新 ZIP 文件的文件名保存在 zipFilename 变量中, 你就可以调用 zipfile.ZipFile() , 实际创建这个 ZIP 文件 。确保传入 'w' 作为 第二个参数,这样 ZIP 文件以写模式打开。

3.7.3. 第3步:遍历目录树并添加到 ZIP 文件

现在需要使用 os.walk() 函数, 列出文件夹以及子文件夹中的每个文件。 让你的程序看起来像这样:

>>> #! python3
>>> # backupToZip.py
>>> # Copies an entire folder and its contents into
>>> # a zip file whose filename increments.
>>>
>>> import zipfile, os
>>>
>>> folder = os.path.abspath('.') # make sure folder is absolute

注意, 不要将备份的结果文件放到要备份的文件夹中,不然备份程序会循环读取备份的结果文件,导致结果文件无限变大,直至充满硬盘空间。

这里为了方便将文件保存到 /tmp 文件夹中。实际使用要放到恰当的位置。

>>> # Figure out the filename this code should used based on
>>> # what files already exist.
>>>
>>>
>>> number = 1
>>> while True:
>>>     zipFilename = '/tmp/xx_' +  os.path.basename(folder) + '_' + str(number) + '.zip'
>>>     if not os.path.exists(zipFilename):
>>>         break
>>>     number = number + 1
>>>
>>>
>>> # Create the zip file.
>>> print('Creating %s...' % (zipFilename))
>>> backupZip = zipfile.ZipFile(zipFilename, 'w')
>>>
>>> # Walk the entire folder tree and compress the files in each folder.
>>> for foldername, subfolders, filenames in os.walk(folder):
>>>     print('Adding files in %s...' % (foldername))
>>>     # Add the current folder to the ZIP file.
>>>     backupZip.write(foldername)
>>>
>>>     # Add all the files in this folder to the ZIP file.
>>>     for filename in filenames:
>>>         if filename.startswith(os.path.basename(folder) + '_') and filename.endswith('.zip'):
>>>             continue # don't backup the backup ZIP files
>>>         backupZip.write(os.path.join(foldername, filename))
>>> backupZip.close()
>>> print('Done.')
Creating /tmp/xx_ch03_file_1.zip...
Adding files in /home/bk/book-jubook/python/jubook_python/pt04_essential/ch03_file...
Adding files in /home/bk/book-jubook/python/jubook_python/pt04_essential/ch03_file/.ipynb_checkpoints...
Done.

可以在 for 循环中使用 os.walk ,在每次迭代中, 它将返回这次迭代当前的文件夹名称、这个文件夹中的子文件夹, 以及这个文件夹中的文件名。

在这个 for 循环中,该文件夹被添加到 ZIP 文件。 嵌套的 for 循环将遍历 filenames 列表中的每个文件。 每个文件都被添加到 ZIP 文件中,以前生成的备份 ZIP 文件除外。

如果运行该程序,它产生的输出看起来像这样:

Creating delicious_1.zip...
Adding files in C:\delicious...
Adding files in C:\delicious\cats...
Adding files in C:\delicious\waffles...
Adding files in C:\delicious\walnut...
Adding files in C:\delicious\walnut\waffles...
Done.

第二次运行它时,它将 C::raw-latex:`delidous `中的所有文件放进一个 ZIP 文件, 命名为 delicious_2.zip,以此类推。

3.7.4. 第4步:类似程序的想法

你可以在其他程序中遍历一个目录树, 将文件添加到压缩的 ZIP 归档文件中。 例如,你可以编程做下面的事情:

  • 遍历一个目录树,将特定扩展名的文件归档,诸如 .txt 或 .py ,并排除其他文件。

  • 遍历一个目录树,将除 .txt 和 .py 文件以外的其他文件归档。

  • 在一个目录树中查找文件夹,它包含的文件数最多,或者使用的磁盘空间最大。

3.7.5. 小结

即使你是一个有经验的计算机用户, 可能也会用鼠标和键盘手工处理文件。 现在的文件浏览器使得处理少量文件的工作很容易。 但有时候,如果用计算机的浏览器, 你需要完成的任务可能要花几个小时。

osshutil 模块提供了一些函数, 用于复制、移动、改名和删除文件。在删除文件时, 你可能希望使用 send2trash 模块, 将文件移动到回收站或垃圾箱, 而不是永久地删除它们。在编程处理文件时, 最好是先注释掉实际会复制/移动/改名/删除 文件的代码,添加 print() 调用, 这样你就可以运行该程序,验证它实际会做什么。

通常,你不仅需要对一个文件夹中的文件执行这些操作, 而是对所有下级子文件夹执行操作。os.walk() 函数 将处理这个艰苦工作,遍历文件夹,这样你就可以专注 于程序需要对其中的文件做什么。

zipfile 模块提供了一种方法,用Python压缩和解压ZIP 归档文件。和 osshutil 模块中的文件处理函数 一起使用,很容易将硬盘上任意位置的一些文件打包。 和许多独立的文件相比,这些 ZIP 文件更容易上传到网站, 或作为 E-mail 附件发送。

本书前面几章提供了源代码让你拷贝。 但如果你编写自己的程序,可能在第一次编写时不会完美无缺。 下一章将聚焦于一些Python模块,它们帮助你分析和调试程序, 这样就能让程序很快正确运行。

3.7.6. 习题

1.shutil.copy()shutil.copytree() 之间的区别是什么?

2.什么函数用于文件改名?

3.send2trashshutil 模块中的删除函数之间的区别是什么?

4.ZipFile 对象有一个 close() 方法,就像 File 对象的 close() 方法。 ZipFile 对象 的什么方法等价于 File 对象的 open() 方法?