我们已经探究了python语言的方方面面,现在我们将通过设计编写一个有用的程序将这些内容有机的结合起来。
主要目标是让大家有能力独自编写程序。
问题
我们要解决的问题是”希望编写一个程序,用于创建所有重要文件的备份”。
尽管这个问题很简单,但并没有给出足够多的直观信息用以创建解决方案。所以进行少量的分析还是必须的。
例如,如何指定哪些文件需要备份?如何存储?存在哪?
适当的分析过问题后,我们开始设计程序。我们创建一个用于指明程序应该如何工作的列表。
在本例中,我已经创建了一个我希望程序如何工作的列表。
如果换作你来设计,你可能不会和我一样分析问题,毕竟每个人都有自己解决问题的思路,这很正常.
1.需要备份的文件和目录由一个列表指定。
2.备份必须存在一个主备份目录中。
3.文件会被备份为一个zip文件。
4.这个zip文件以当前的日期和时间命名。
5.我们使用任何标准linux/unix发行版中默认的标准zip命令创建zip文件。
Windows用户可以从GnuWin32工程页下载安装之,并将C:/Program Files/GnuWin32/bin添加到你的系统环境变量PATH中。
GnuWin32工程页: http://gnuwin32.sourceforge.net/packages/zip.htm
zip命令下载: http://gnuwin32.sourceforge.net/downlinks/zip.php
注意你可以使用任何希望的存档命令,只要它拥有一个命令行接口。因此我们可以通过脚本为它传送参数。
解决方案
现在我们的程序已经设计妥当,下一步可以着手编写实现我们的解决方案的代码了。
#!/usr/bin/python
# Filename: backup_ver1.py
import os
import time
# 1. 需要备份的文件和目录由一个列表指定
source = ['"C://My Documents"', 'C://Code']
# 注意我们必须在字符串内部使用双引号将带有空格的名字括起来。
# 2. 备份必须存在一个主备份目录中
target_dir = 'E://Backup' # 记住改变这里即可改变你想要使用的主目录
# 3. 文件会被备份为一个zip文件。
# 4. 这个zip文件以当前的日期和时间命名。
target = target_dir + os.sep + time.strftime('%Y%m%d%H%M%S') + '.zip'
# 5. 我们使用zip命令将文件归档成一个zip
zip_command = "zip -qr {0} {1}".format(target, ' '.join(source))
# 执行备份
if os.system(zip_command) == 0:
print('Successful backup to', target)
else:
print('Backup FAILED')
输出:
$ python backup_ver1.py
Successful backup to E:/Backup/20080702185040.zip
现在,我们正在测试阶段测试我们的程序是否正确工作,如果与预期不符,那么我不得不对程序debug,即消除程序中的bug(错误)。
如果上面的程序在你那无法工作,那么在调用os.system前添加一句print(zip_command)并运行程序。
现在将打印出的zip_command的值拷贝/复制到命令行看看它是否能正确运行。
如果这个命令还是运行失败,查阅zip命令手册吧确定是哪里出了问题。
相反如果命令运行成功,请仔细核对你输入的代码与上面的程序是否相同。
代码如何工作:
接下来你将注意到我们是如何将我们的设计一步一步转换为代码的。
我们将使用os和time模块,所以最先导入了它们。然后我们在source列表中指定需要备份的文件和目录。
目标目录用来存储所有的备份文件,其由target_dir变量指定。
我们将要创建的zip归档文件的文件名为当前的日期和时间,这通过time.strftime()函数得到。
归档文件还拥有.zip后缀名并被存储到target_dir指定的目录中。
注意os.sep变量的使用 - 这给予目录分隔符以系统无关性,即它在linux, unix为’/’,在windows为’//’而在Mac OS是’:’。
使用os.sep代替直接使用这些字符将使我们的程序可移植的跨多系统工作。
time.strftime()函数需要一个类似上面程序中使用的说明符。
说明符%Y会被带有世纪部分的年份替换(注: 2010即带有世纪部分,而10则不带有,此处原文是”%Y不带有世纪部分”这是错误的,%y才不带有)。
%m会被以为十进制数01到12表示的月份替换,后面的说明符以此类推。
完整的说明符列表可以在python参考手册中找到。(http://docs.python.org/dev/3.0/library/time. html#time. strftime)。
(注:这个地址好像挂了,不然得翻墙?查本地手册是一样一样地)
我们使用加法运算符连接字符串以创建目标zip文件名,这里的加法运算符用于将两个字符串连接起来并返回这个连接后的字符串。
然后我们创建字符串zip_command,其中包括我们将要执行的命令。你可以在shell(linux终端或dos命令行)中运行这个命令检查它是否正常工作。
我们使用的zip命令带有一些选项和参数。-q选项指示zip将以安静模式工作。-r指定命令将递归的压缩目录,即包括指定目录的所有子目录和文件。
两个选项可以简写组合到一起-qr。选项后面紧跟被创建的zip归档文件的文件名和需要备份的文件与目录。
我们通过字符串的join方法将列表source合并成一个字符串,join的使用方法我们已经讲过了。
最后我们使用os.system函数运行命令,这就好像从系统(shell)运行命令一样 – 如果命令成功则返回0,否则返回错误号。
根据命令运行的结果,我们打印出适当的信息提示备份是否成功。
至此,我们终于完成了这个备份重要文件的脚本!
写给windows用户
作为反斜杠转义序列的替代,你可以使用原始字符串。例如’C://Documents’或者r’C:/Documents’。
但是不要使用’C:/Documents’因为这样做实际上是在使用一个未定义的转义序列/D。
既然我们已经拥有一个可用的备份脚本,那么当我们需要备份文件的时候都可以使用它。
建议linux/unix用户使用前面介绍过的执行方法,这样就可以在任何时间任何地点运行备份脚本了。这被称作软件的操作阶段或部署阶段。
上面程序可以正确运行,但通常第一版程序不会完全达到你的预期。
例如如果你没有正确的设计程序或是输入了错误代码等等都会造成问题。这时你将不得不返回设计阶段或是为程序debug。
第二版程序
我们的第一版程序可以工作。不过仍有一些改良空间使得它在日常使用中更好的工作。这称作软件的维护阶段。
其中我认为比较有用的一个改良是提供更好的文件命名机制 – 在主备份目录中,以时间作为文件名而以日期作为目录名。
这样做的优点之一是你的备份被分级存放,因此变得更容易管理。第二文件名变的更短。
第三个优点是分离各个目录将帮助你轻松的检查当天是否创建了备份,因为只有当天创建了备份相应的目录才会被创建。
#!/usr/bin/python
# Filename: backup_ver2.py
import os
import time
# 1. 需要备份的文件和目录由一个列表指定
source = ['"C://My Documents"', 'C://Code']
# 注意我们必须在字符串内部使用双引号将带有空格的名字括起来。
# 2. 备份必须存在一个主备份目录中
target_dir = 'E://Backup' # 记住改变这里即可改变你想要使用的主目录
# 3. 文件会被备份为一个zip文件。
# 4. 主目录中的子目录将以当前日期命名
today = target_dir + os.sep + time.strftime('%Y%m%d')
# zip归档文件将以当前时间命名
now = time.strftime('%H%M%S')
# 如果子目录不存在则创建之
if not os.path.exists(today):
os.mkdir(today) # make directory
print('Successfully created directory', today)
# 组成zip文件名
target = today + os.sep + now + '.zip'
# 5. 我们使用zip命令将文件归档成一个zip
zip_command = "zip -qr {0} {1}".format(target, ' '.join(source))
# 执行备份
if os.system(zip_command) == 0:
print('Successful backup to', target)
else:
print('Backup FAILED')
输出:
$ python backup_ver2.py
Successfully created directory E:/Backup/20080702
Successful backup to E:/Backup/20080702/202311.zip
$ python backup_ver2.py
Successful backup to E:/Backup/20080702/202325.zip
代码如何工作:
程序的大部分与第一版相同。主要改变是我们使用os.path.exists函数检查主备份目录中否存在一个与当前日期同名的子目录。
如果不存在则利用os.mkdir函数创建之。
第三版程序
我用第二版程序做了许多备份,它工作的很好,但当备份太多时我发现很难彼此区分它们!
例如,我可能对一个程序或演示稿做了某些重要修改,并希望将这些改变关联到zip归档文件名上。
这可以通过为zip归档文件名附加一个用户注释轻松做到。
注意
下面的程序并不能工作,所以不要疑惑请阅读下去,在此它只是一个演示。
#!/usr/bin/python
# Filename: backup_ver3.py
import os
import time
# 1. 需要备份的文件和目录由一个列表指定
source = ['"C://My Documents"', 'C://Code']
# 注意我们必须在字符串内部使用双引号将带有空格的名字括起来
# 2. 备份必须存在一个主备份目录中
target_dir = 'E://Backup' # 记住改变这里即可改变你想要使用的主目录
# 3. 文件会被备份为一个zip文件
# 4. 主目录中的子目录将以当前日期命名
today = target_dir + os.sep + time.strftime('%Y%m%d')
# zip归档文件将以当前时间命名
now = time.strftime('%H%M%S')
# 让用户输入一个注释以便创建zip文件名
comment = input('Enter a comment --> ')
if len(comment) == 0: # 检查是否输入注释
target = today + os.sep + now + '.zip'
else:
target = today + os.sep + now + '_' +
comment.replace(' ', '_') + '.zip'
# 如果子目录不存在则创建之
if not os.path.exists(today):
os.mkdir(today) # 创建目录
print('Successfully created directory', today)
# 5. 我们使用zip命令将文件归档成一个zip
zip_command = "zip -qr {0} {1}".format(target, ' '.join(source))
# 运行脚本
if os.system(zip_command) == 0:
print('Successful backup to', target)
else:
print('Backup FAILED')
输出:
$ python backup_ver3.py
File "backup_ver3.py", line 25
target = today + os.sep + now + '_' +
^
SyntaxError: invalid syntax
代码如何运行:
记住这个程序无法工作!python说它遇到一个语法错误,这表示脚本不符合python预期的语法结构。
当我们留意错误信息时还发现python同时告诉我们错误出在哪里。所以我们从这个错误行开始debug。
仔细观察后我们注意到一个逻辑行被分割成两个物理行,但我们并指出这两个物理行是连一起的。
基本上,python发现了加法运算符(+)但没有在这个逻辑行上找到需要的运算数,因此python不知道该如何继续下去。
记住在物理行末尾使用反斜杠我们可以指定逻辑行将延续到下个物理行。
因此我们修正这个错误,这叫做错误修正(bug fixing)。
第四版程序
#!/usr/bin/python
# Filename: backup_ver4.py
import os
import time
# 1. 需要备份的文件和目录由一个列表指定
source = ['"C://My Documents"', 'C://Code']
# 注意我们必须在字符串内部使用双引号将带有空格的名字括起来
# 2. 备份必须存在一个主备份目录中
target_dir = 'E://Backup' # 记住改变这里即可改变你想要使用的主目录
# 3. 文件会被备份为一个zip文件.
# 4. 主目录中的子目录将以当前日期命名
today = target_dir + os.sep + time.strftime('%Y%m%d')
# zip归档文件将以当前时间命名
now = time.strftime('%H%M%S')
# 让用户输入一个注释以便创建zip文件名
comment = input('Enter a comment --> ')
if len(comment) == 0: # 检查是否输入注释
target = today + os.sep + now + '.zip'
else:
target = today + os.sep + now + '_' + /
comment.replace(' ', '_') + '.zip'
# 如果子目录不存在则创建之
if not os.path.exists(today):
os.mkdir(today) # 创建目录
print('Successfully created directory', today)
# 5. 我们使用zip命令将文件归档成一个zip
zip_command = "zip -qr {0} {1}".format(target, ' '.join(source))
# 执行脚本
if os.system(zip_command) == 0:
print('Successful backup to', target)
else:
print('Backup FAILED')
输出:
$ python backup_ver4.py
Enter a comment --> added new examples
Successful backup to
E:/Backup/20080702/202836_added_new_examples.zip
$ python backup_ver4.py
Enter a comment -->
Successful backup to E:/Backup/20080702/202839.zip
代码如何工作:
程序现在可以工作了!让我们看看在第三版中所做的实质性增强吧。
我们使用input函数得到用户注释,然后检查用户是否真的输入了什么,这可以通过len函数判断输入长度做到。
如果用户什么都没输入(也许用户只是需要一个常规备份或没有对文件进行什么特别的修改),则程序就像老版本那样工作。
相反,如果用户提供了注释,则注释会被附加到zip归档文件的扩展名.zip之前。
注意我使用下划线替换注释中的空格 – 这样文件名管理起来更简单。
更多改进
第四版程序对于大多数用户已经是个令人满意的脚本了,但程序永远存在可以改进的空间。
例如,你可以为程序增加一个冗言层(verbosity level)并以-v选项启动,使得你的程序更多嘴(注:也就是让程序的交互性更强)。
另一个可能的改进是在命令行直接将文件和目录传给脚本。这些文件和目录名可以通过sys.argv列表得到并使用list类的extend方法将它们添加到source列表。
而最重要的一个改进可能就是用内建模块zipfile或tarfile代替os.system创建归档文件了。它们是标准库的一部分而且无需依赖你的计算机安装外部zip程序。
无论如何,在上面的示例中出于教学目的我完全使用os.system创建备份,这很简单明了足以让任何人理解但并不是令人满意的实现方式。
那么你能不能使用zipfile模块代替os.system实现程序的第五版呢?(注:在官方文档里查zipfile的使用方法)
软件开发过程
现在我们已经走过了编写软件的各个阶段。这些阶段总结如下:
1. 需要什么功能(分析)
2. 如何实现它们(设计)
3. 着手实现它们(实现)
4. 测试这些功能(测试和debug)
5. 使用(实施或部署)
6. 维护程序(改良)
我们创建这个备份脚本的步骤就是编写一个程序时被推荐的方式 - 首先分析和设计。然后实现一个简单版本。
测试并debug确定它能够如期工作。最后增加你需要的新特性并重复编写-测试-使用的过程直到你满意为止。
记住,软件是扩展起来的,而不是建造起来的。
小结
我们已经看到如何编写自己的python程序/脚本,并见到编写一个类似程序所牵涉到的各个步骤。
你会发现这些知识对于创建自己的程序会非常有用,因此你不仅掌握了python也掌握了解决问题的办法。
接下来,我们将讨论面向对象编程。