我們已經(jīng)研究了 Python 語言的眾多內(nèi)容,現(xiàn)在我們將來學習一下怎么把這些內(nèi)容結(jié)合起來。我們將設(shè)計編寫一個能夠 做 一些確實有用的事情的程序。
我提出的問題是: 我想要一個可以為我的所有重要文件創(chuàng)建備份的程序。
盡管這是一個簡單的問題,但是問題本身并沒有給我們足夠的信息來解決它。進一步的分析是必需的。例如,我們?nèi)绾未_定該備份哪些文件?備份保存在哪里?我們怎么樣存儲備份?
在恰當?shù)胤治隽诉@個問題之后,我們開始設(shè)計我們的程序。我們列了一張表,表示我們的程序應(yīng)該如何工作。對于這個問題,我已經(jīng)創(chuàng)建了下面這個列表以說明 我 如何讓它工作。如果是你設(shè)計的話,你可能不會這樣來解決問題——每個人都有其做事的方法,這很正常。
需要備份的文件和目錄由一個列表指定。
備份應(yīng)該保存在主備份目錄中。
文件備份成一個 zip 文件。
zip 存檔的名稱是當前的日期和時間。
當我們基本完成程序的設(shè)計,我們就可以編寫代碼了,它是對我們的解決方案的實施。
例 10.1 備份腳本——版本一
#!/usr/bin/python
# Filename: backup_ver1.py
import os
import time
# 1. The files and directories to be backed up are specified in a list.
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# If you are using Windows, use source = [r'C:\Documents', r'D:\Work'] or something like that
# 2. The backup must be stored in a main backup directory
target_dir = '/mnt/e/backup/' # Remember to change this to what you will be using
# 3. The files are backed up into a zip file.
# 4. The name of the zip archive is the current date and time
target = target_dir + time.strftime('%Y%m%d%H%M%S') + '.zip'
# 5. We use the zip command (in Unix/Linux) to put the files in a zip archive
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))
# Run the backup
if os.system(zip_command) == 0:
print 'Successful backup to', target
else:
print 'Backup FAILED'
(源文件:code/backup_ver1.py)
輸出
$ python backup_ver1.py
Successful backup to /mnt/e/backup/20041208073244.zip
現(xiàn)在,我們已經(jīng)處于測試環(huán)節(jié)了,在這個環(huán)節(jié),我們測試我們的程序是否正確工作。如果它與我們所期望的不一樣,我們就得調(diào)試我們的程序,即消除程序中的 瑕疵 (錯誤)。
它如何工作
接下來你將看到我們?nèi)绾伟?設(shè)計 一步一步地轉(zhuǎn)換為 代碼 。
我們使用了 os 和 time 模塊,所以我們輸入它們。然后,我們在 source 列表中指定需要備份的文件和目錄。目標目錄是我們想要存儲備份文件的地方,它由 target_dir 變量指定。zip 歸檔的名稱是目前的日期和時間,我們使用 time.strftime()函數(shù)獲得。它還包括.zip 擴展名,將被保存在 target_dir 目錄中。
time.strftime()函數(shù)需要我們在上面的程序中使用的那種定制。%Y 會被無世紀的年份所替代。%m 會被 01 到 12 之間的一個十進制月份數(shù)替代,其他依次類推。這些定制的詳細情況可以在《Python 參考手冊》中獲得?!禤ython 參考手冊》包含在你的 Python 發(fā)行版中。注意這些定制與用于 print 語句的定制(%后跟一個元組)類似(但不完全相同)
我們使用加法操作符來 級連 字符串,即把兩個字符串連接在一起返回一個新的字符串。通過這種方式,我們創(chuàng)建了目標 zip 文件的名稱。接著我們創(chuàng)建了 zip_command 字符串,它包含我們將要執(zhí)行的命令。你可以在 shell(Linux 終端或者 DOS 提示符)中運行它,以檢驗它是否工作。
zip 命令有一些選項和參數(shù)。-q 選項用來表示 zip 命令安靜地工作。-r 選項表示 zip 命令對目錄遞歸地工作,即它包括子目錄以及子目錄中的文件。兩個選項可以組合成縮寫形式-qr。選項后面跟著待創(chuàng)建的 zip 歸檔的名稱,然后再是待備份的文件和目錄列表。我們使用已經(jīng)學習過的字符串 join 方法把 source 列表轉(zhuǎn)換為字符串。
最后,我們使用 os.system 函數(shù) 運行 命令,利用這個函數(shù)就好像在 系統(tǒng) 中運行命令一樣。即在 shell 中運行命令——如果命令成功運行,它返回 0,否則它返回錯誤號。
根據(jù)命令的輸出,我們打印對應(yīng)的消息,顯示備份是否創(chuàng)建成功。好了,就是這樣我們已經(jīng)創(chuàng)建了一個腳本來對我們的重要文件做備份!
給 Windows 用戶的注釋
你可以把 source 列表和 target 目錄設(shè)置成任何文件和目錄名,但是在 Windows 中你得小心一些。問題是 Windows 把反斜杠(\)作為目錄分隔符,而 Python 用反斜杠表示轉(zhuǎn)義符! 所以,你得使用轉(zhuǎn)義符來表示反斜杠本身或者使用自然字符串。例如,使用'C:\Documents'或r'C:\Documents'而不是'C:\Documents'——你在使用一個不知名的轉(zhuǎn)義符\D!
現(xiàn)在我們已經(jīng)有了一個可以工作的備份腳本,我們可以在任何我們想要建立文件備份的時候使用它。建議 Linux/Unix 用戶使用前面介紹的可執(zhí)行的方法,這樣就可以在任何地方任何時候運行備份腳本了。這被稱為軟件的實施環(huán)節(jié)或開發(fā)環(huán)節(jié)。
上面的程序可以正確工作,但是(通常)第一個程序并不是與你所期望的完全一樣。例如,可能有些問題你沒有設(shè)計恰當,又或者你在輸入代碼的時候發(fā)生了一點錯誤,等等。正常情況下,你應(yīng)該回到設(shè)計環(huán)節(jié)或者調(diào)試程序。
第一個版本的腳本可以工作。然而,我們可以對它做些優(yōu)化以便讓它在我們的日常工作中變得更好。這稱為軟件的維護環(huán)節(jié)。
我認為優(yōu)化之一是采用更好的文件名機制——使用 時間 作為文件名,而當前的 日期 作為目錄名,存放在主備份目錄中。這樣做的一個優(yōu)勢是你的備份會以等級結(jié)構(gòu)存儲,因此它就更加容易管理了。另外一個優(yōu)勢是文件名的長度也可以變短。還有一個優(yōu)勢是采用各自獨立的文件夾可以幫助你方便地檢驗?zāi)闶欠裨诿恳惶靹?chuàng)建了備份,因為只有在你創(chuàng)建了備份,才會出現(xiàn)那天的目錄。
例 10.2 備份腳本——版本二
#!/usr/bin/python
# Filename: backup_ver2.py
import os
import time
# 1. The files and directories to be backed up are specified in a list.
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# If you are using Windows, use source = [r'C:\Documents', r'D:\Work'] or something like that
# 2. The backup must be stored in a main backup directory
target_dir = '/mnt/e/backup/' # Remember to change this to what you will be using
# 3. The files are backed up into a zip file.
# 4. The current day is the name of the subdirectory in the main directory
today = target_dir + time.strftime('%Y%m%d')
# The current time is the name of the zip archive
now = time.strftime('%H%M%S')
# Create the subdirectory if it isn't already there
if not os.path.exists(today):
os.mkdir(today) # make directory
print 'Successfully created directory', today
# The name of the zip file
target = today + os.sep + now + '.zip'
# 5. We use the zip command (in Unix/Linux) to put the files in a zip archive
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))
# Run the backup
if os.system(zip_command) == 0:
print 'Successful backup to', target
else:
print 'Backup FAILED'
(源文件:code/backup_ver2.py)
輸出
$ python backup_ver2.py
Successfully created directory /mnt/e/backup/20041208
Successful backup to /mnt/e/backup/20041208/080020.zip
$ python backup_ver2.py
Successful backup to /mnt/e/backup/20041208/080428.zip
它如何工作
兩個程序的大部分是相同的。改變的部分主要是使用 os.exists 函數(shù)檢驗在主備份目錄中是否有以當前日期作為名稱的目錄。如果沒有,我們使用 os.mkdir 函數(shù)創(chuàng)建。
注意 os.sep 變量的用法——這會根據(jù)你的操作系統(tǒng)給出目錄分隔符,即在 Linux、Unix 下它是'/',在 Windows 下它是'\',而在 Mac OS 下它是':'。使用 os.sep 而非直接使用字符,會使我們的程序具有移植性,可以在上述這些系統(tǒng)下工作。
第二個版本在我做較多備份的時候還工作得不錯,但是如果有極多備份的時候,我發(fā)現(xiàn)要區(qū)分每個備份是干什么的,會變得十分困難!例如,我可能對程序或者演講稿做了一些重要的改變,于是我想要把這些改變與 zip 歸檔的名稱聯(lián)系起來。這可以通過在 zip 歸檔名上附帶一個用戶提供的注釋來方便地實現(xiàn)。
例 10.3 備份腳本——版本三(不工作?。?/strong>
#!/usr/bin/python
# Filename: backup_ver3.py
import os
import time
# 1. The files and directories to be backed up are specified in a list.
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# If you are using Windows, use source = [r'C:\Documents', r'D:\Work'] or something like that
# 2. The backup must be stored in a main backup directory
target_dir = '/mnt/e/backup/' # Remember to change this to what you will be using
# 3. The files are backed up into a zip file.
# 4. The current day is the name of the subdirectory in the main directory
today = target_dir + time.strftime('%Y%m%d')
# The current time is the name of the zip archive
now = time.strftime('%H%M%S')
# Take a comment from the user to create the name of the zip file
comment = raw_input('Enter a comment --> ')
if len(comment) == 0: # check if a comment was entered
target = today + os.sep + now + '.zip'
else:
target = today + os.sep + now + '_' +
comment.replace(' ', '_') + '.zip'
# Create the subdirectory if it isn't already there
if not os.path.exists(today):
os.mkdir(today) # make directory
print 'Successfully created directory', today
# 5. We use the zip command (in Unix/Linux) to put the files in a zip archive
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))
# Run the backup
if os.system(zip_command) == 0:
print 'Successful backup to', target
else:
print 'Backup FAILED'
(源文件:code/backup_ver3.py)
輸出
$ python backup_ver3.py
File "backup_ver3.py", line 25
target = today + os.sep + now + '_' +
^
SyntaxError: invalid syntax
它如何(不)工作
這個程序不工作!Python 說有一個語法錯誤,這意味著腳本不滿足 Python 可以識別的結(jié)構(gòu)。當我們觀察 Python 給出的錯誤的時候,它也告訴了我們它檢測出錯誤的位置。所以我們從那行開始 調(diào)試 我們的程序。
通過仔細的觀察,我們發(fā)現(xiàn)一個邏輯行被分成了兩個物理行,但是我們并沒有指明這兩個物理行屬于同一邏輯行?;旧?,Python 發(fā)現(xiàn)加法操作符(+)在那一邏輯行沒有任何操作數(shù),因此它不知道該如何繼續(xù)。記住我們可以使用物理行尾的反斜杠來表示邏輯行在下一物理行繼續(xù)。所以,我們修正了程序。這被稱為修訂。
例 10.4 備份腳本——版本四
#!/usr/bin/python
# Filename: backup_ver4.py
import os
import time
# 1. The files and directories to be backed up are specified in a list.
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# If you are using Windows, use source = [r'C:\Documents', r'D:\Work'] or something like that
# 2. The backup must be stored in a main backup directory
target_dir = '/mnt/e/backup/' # Remember to change this to what you will be using
# 3. The files are backed up into a zip file.
# 4. The current day is the name of the subdirectory in the main directory
today = target_dir + time.strftime('%Y%m%d')
# The current time is the name of the zip archive
now = time.strftime('%H%M%S')
# Take a comment from the user to create the name of the zip file
comment = raw_input('Enter a comment --> ')
if len(comment) == 0: # check if a comment was entered
target = today + os.sep + now + '.zip'
else:
target = today + os.sep + now + '_' + \
comment.replace(' ', '_') + '.zip'
# Notice the backslash!
# Create the subdirectory if it isn't already there
if not os.path.exists(today):
os.mkdir(today) # make directory
print 'Successfully created directory', today
# 5. We use the zip command (in Unix/Linux) to put the files in a zip archive
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))
# Run the backup
if os.system(zip_command) == 0:
print 'Successful backup to', target
else:
print 'Backup FAILED'
(源文件:code/backup_ver4.py)
輸出
$ python backup_ver4.py
Enter a comment --> added new examples
Successful backup to /mnt/e/backup/20041208/082156_added_new_examples.zip
$ python backup_ver4.py
Enter a comment -->
Successful backup to /mnt/e/backup/20041208/082316.zip
它如何工作
這個程序現(xiàn)在工作了!讓我們看一下版本三中作出的實質(zhì)性改進。我們使用 raw_input 函數(shù)得到用戶的注釋,然后通過 len 函數(shù)找出輸入的長度以檢驗用戶是否確實輸入了什么東西。如果用戶只是按了回車(比如這只是一個慣例備份,沒有做什么特別的修改),那么我們就如之前那樣繼續(xù)操作。
然而,如果提供了注釋,那么它會被附加到 zip 歸檔名,就在.zip 擴展名之前。注意我們把注釋中的空格替換成下劃線——這是因為處理這樣的文件名要容易得多。
對于大多數(shù)用戶來說,第四個版本是一個滿意的工作腳本了,但是它仍然有進一步改進的空間。比如,你可以在程序中包含 交互 程度——你可以用-v選項來使你的程序更具交互性。
另一個可能的改進是使文件和目錄能夠通過命令行直接傳遞給腳本。我們可以通過 sys.argv 列表來獲取它們,然后我們可以使用 list 類提供的 extend 方法把它們加到 source 列表中去。
我還希望有的一個優(yōu)化是使用 tar 命令替代 zip 命令。這樣做的一個優(yōu)勢是在你結(jié)合使用 tar 和 gzip 命令的時候,備份會更快更小。如果你想要在 Windows 中使用這些歸檔,WinZip 也能方便地處理這些.tar.gz 文件。tar 命令在大多數(shù) Linux/Unix 系統(tǒng)中都是默認可用的。Windows 用戶也可以下載安裝它。
命令字符串現(xiàn)在將稱為:
tar = 'tar -cvzf %s %s -X /home/swaroop/excludes.txt' % (target, ' '.join(srcdir))
選項解釋如下:
-c 表示創(chuàng)建一個歸檔。
-v 表示交互,即命令更具交互性。
-z 表示使用 gzip 濾波器。
-f 表示強迫創(chuàng)建歸檔,即如果已經(jīng)有一個同名文件,它會被替換。
重要
最理想的創(chuàng)建這些歸檔的方法是分別使用 zipfile 和 tarfile。它們是 Python 標準庫的一部分,可以供你使用。使用這些庫就避免了使用 os.system 這個不推薦使用的函數(shù),它容易引發(fā)嚴重的錯誤。 然而,我在本節(jié)中使用 os.system 的方法來創(chuàng)建備份,這純粹是為了教學的需要。這樣的話,例子就可以簡單到讓每個人都能夠理解,同時也已經(jīng)足夠用了。
現(xiàn)在,我們已經(jīng)走過了編寫一個軟件的各個環(huán)節(jié)。這些環(huán)節(jié)可以概括如下:
重要
我們創(chuàng)建這個備份腳本的過程是編寫程序的推薦方法——進行分析與設(shè)計。開始時實施一個簡單的版本。對它進行測試與調(diào)試。使用它以確信它如預(yù)期那樣地工作。再增加任何你想要的特性,根據(jù)需要一次次重復(fù)這個編寫-測試-使用的周期。記住“軟件是長出來的,而不是建造的”。
我們已經(jīng)學習如何創(chuàng)建我們自己的 Python 程序/腳本,以及在編寫這個程序中所設(shè)計到的不同的狀態(tài)。你可以發(fā)現(xiàn)它們在創(chuàng)建你自己的程序的時候會十分有用,讓你對 Python 以及解決問題都變得更加得心應(yīng)手。
接下來,我們將討論面向?qū)ο蟮木幊獭?/p>