我們已經探究了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也掌握了解決問題的辦法。
接下來,我們將討論面向對象編程。

