Python編程金典

《Python編程金典》讀書筆記


Table of Contents

1. 緒論
2. python編程概述
2.1. 知識點
2.2. 良好的編程習慣
2.3. 常見編程錯誤
2.4. 測試和調試提示
2.5. 移植性提示
3. 控制流程
3.1. 知識點
3.2. 良好的編程習慣
3.3. 常見編程錯誤
3.4. 移植性提示
3.5. 軟件工程知識
4. 函數
4.1. 知識點
4.2. 良好的編程習慣
4.3. 常見編程錯誤
4.4. 移植性提示
4.5. 軟件工程知識
4.6. 性能提示
5. 列表、元組和字典
5.1. 知識點
6. 公共網關接口(CGI)入門
6.1. 知識點
7. 基於面向對象的編程
7.1. 知識點
7.2. 良好的編程習慣
7.3. 常見編程錯誤
7.4. 測試和調試提示
7.5. 軟件工程知識
7.6. 性能提示
8. 自定義類
8.1. 知識點
8.2. 良好的編程習慣
8.3. 常見編程錯誤
8.4. 軟件工程知識
8.5. 性能提示
9. 面向對象編程:繼承
9.1. 知識點
9.2. 常見編程錯誤
9.3. 軟件工程知識
9.4. 性能提示
10. 圖形用戶界面組件(一)
10.1. 知識點
10.2. 良好的編程習慣
10.3. 常見編程錯誤
10.4. 界面知識
11. 圖形用戶界面組件(二)
11.1. 知識點
11.2. 測試和調試提示
11.3. 界面知識
12. 異常處理
12.1. 知識點
12.2. 良好的編程習慣
12.3. 常見編程錯誤
12.4. 測試和調試提示
12.5. 軟件工程知識
12.6. 性能提示
13. 字符串處理和正則表達式
13.1. 知識點
13.2. 良好的編程習慣
13.3. 性能提示
14. 文件處理和序列化
14.1. 知識點
14.2. 良好編程習慣
14.3. 常見編程錯誤
14.4. 性能提示
15. 可擴展標記語言(XML)
15.1. 知識點
15.2. 常見編程錯誤
15.3. 移植性提示
15.4. 軟件工程知識
15.5. 性能提示
15.6. 示例
16. Python的XML處理
16.1. 知識點
16.2. 良好編程習慣
16.3. 示例
17. 數據庫應用程序編程接口(DB-API)
17.1. 知識點
17.2. 良好的編程習慣
17.3. 常見編程錯誤
17.4. 移植性提示
18. 進程管理
18.1. 知識點
18.2. 良好的編程習慣
18.3. 移植性提示
19. 多線程處理
19.1. 知識點
19.2. 性能提示
19.3. 常見編程錯誤
19.4. 測試和調試提示
19.5. 性能提示
20. 聯網
20.1. 知識點
20.2. 常見編程錯誤
20.3. 軟件工程知識
20.4. 性能提示

Chapter 1. 緒論

Chapter 2. python編程概述

2.1. 知識點

  1. raw_input是python的內建函數,要求用戶輸入,輸入結果是一個字符串。 example: test = raw_input("please input a number:/n")

  2. python是一種區分大小寫的語言。

  3. id函數返回變量內存位置,type函數返回變量類型。

  4. 在python2.2前,只提供一種除法運算符(/),運算的行爲(即是Floor整數除法,還是 True浮點除法)是由操作數的類型來決定的。如果操作數全是整數,就執行Floor除法。如 一個或兩個操作數是浮點數,就執行True浮點除法。

  5. 在python 2.2後的所有版本中,設計者決定去除(/)的隨意性。決定採用兩個操作符,其 中/執行True除法;//執行Floor除法。但這樣會造成舊版本的程序出錯,所以設計者採取了 一種折衷的辦法。如果不先聲明,python還是使用舊的/操作符。如果要使用新的方法就要 進行聲明,聲明方式是: from __future__ import division,這樣就可以用/ 和 //了。

  6. 格式化字符串 print "test is %d" % integer1,表示方法和c語言差不多。

  7. 如果語句太長需要用到“/”這個續行符。

  8. 在過程式編程中,程序員把重點放在寫函數上,用於執行一些任務的行動被組合成函數, 不同的函數進一步進行組合,即構成程序。

  9. 採用面向對象編程,程序員的工作主要放在創建自已的“類”上。每個類包含數據及一 系列函數。類的數據組件被稱爲數據成員或屬性,類的函數組件則稱爲方法。

  10. 重用,重用,再重用是影響軟件開發三大因素。就如影響房地產價格三大因素是地段, 地段,不是地段。

2.2. 良好的編程習慣

  1. 在程序中使用豐富的註釋。註釋有助於其他程序員理解程序,有助於程序員調試,並列 出有用的信息。以後修改或更新代碼時,註釋還有助於你理解自已當初編寫的程序。

  2. 每個程序都應以一條註釋開始,描述該程序的用途。

  3. 加一些空行來增強程序的可讀性。

  4. 有意義的變量名可改善程序的“自編檔能力”,也就是說,只需讀一讀程序,就能輕鬆 理解它。

  5. 避免標識符以下劃線和雙下劃線開頭,因爲python解釋器可能保留了那些名稱,供內部 使用。

  6. 在二元運算符兩端添加一個空格。這樣可以突出運算符,增強程序的可讀性。

  7. 和代數一樣,可在表達式中添加原本不需要的括號,使其更清晰。

2.3. 常見編程錯誤

  1. 試圖訪問一個未賦值的變量,會產生運行時錯誤。

  2. 不要把“==”相等和“=”賦值運算符操混了。賦值符號“=”不能出現在if等的條件 語句中。

  3. 忘記在if結構中插入冒號是語法錯誤。if a == b: ...

  4. 不要忘記了縮進格式,否則會出現語法錯誤。由於tab鍵在不同系統中的長度不同,所 以建議將3個空格定爲一個縮進級別。如果在一個程序中縮進量不同,會造成語法錯誤。

2.4. 測試和調試提示

  1. 使用 -i 選項(python -i test.py)。會導致編譯器在執行了文件中的語句後進行交互模式, 這非常適用於調試程序。

  2. 爲了避免難以察覺的錯誤,務必在程序中採用統一和正確的縮進。

2.5. 移植性提示

  1. 預計在python 3.0中,運算符/只執行True除法。3.0發佈後,程序需更新自已的程序。

Chapter 3. 控制流程

3.1. 知識點

  1. 所謂“算法”,是指解決一個問題的“過程”,它包含兩個含義,1是要採取的行動,2 是採取這些行動的順序。

  2. 研究表明,只要三種結構就可以寫出所有程序,這三種結構是:順序結構,選擇結構以 及重複結構。

  3. 算法求精,就是把算法用僞代碼逐層分解成可以用python程序實現的過程。

  4. +=符號將符號右邊的表達式的值加到左邊的變量上,再將結果存回左邊的變量。變量要 先初始化,如果沒有會出錯。

  5. range(0,10,1)代表一個從0到9 共10個元素的序列,自增量爲1。

  6. 與非結構化程序相比,結構化編程所生成的程序要容量理解得多,所以更易測試、調試 和修改,而且不易出錯。

3.2. 良好的編程習慣

  1. 初始化所有變量。

  2. 在信號值控制的循環中,當提示輸入時,應明確指明信息值是哪一個。

  3. 當執行除法運算時,如除數可能爲零,請務必明確檢測。關在程序中進行相應處理,不 要任由錯誤發生。

  4. 在每個控制結構前後各留一個空行,將其同程序的其餘部份區分開。

  5. 嵌套級別過多,會使程序難以理解,通常應將嵌套控制在3級以內。

  6. 避免在for循環主體更改控制變量的值,這有可能導致不易發現的邏輯錯誤。

3.3. 常見編程錯誤

  1. 將所有浮點數假設爲是精確的,會導致不正確的結果。浮點數在大多數計算機中只是近 似數。

  2. 在賦值符號左邊的變量初始化之前試圖使用增量賦值是錯誤的。

  3. 如果忘記range函數返回的序列的第一個值是0,可以導致差1錯誤。

3.4. 移植性提示

  1. python 2.0開始引入增量賦值符號,在老版本中python中使用增量賦值符號是語法錯誤。

3.5. 軟件工程知識

  1. 經驗表明,用計算機解決問題最有效的辦法是爲解決方案開發一種算法。一旦開發出正 確的算法,通常能根據它方便地生成一個能實際工作的python程序。

  2. 在從事大型的、複雜的項目時,一定要開發算法。這樣纔可能不會導致嚴重錯誤,從而 推遲項目進度。

Chapter 4. 函數

4.1. 知識點

  1. python的程序組件包括函數、類、模塊和包。“模塊”是包含函數和類定義的文件。許多 模塊可以組合成一個集合,稱爲“包”。

  2. 模塊位於python安裝目錄的庫目錄下。在unix/linux下,是/usr/lib/python2.2或 /usr/local/lib/python2.2,在windows上,則是/python/lib。

  3. 函數定義中創建的所有變量都是“局部變量”--只存在於聲明它們的函數中。

  4. python定義了3個命名空間,分別是局部(local),全局(global)和內建(built-in)。程 序訪問標識符的值時,python會按特定順序搜索命名空間(即按局部,全局和內建順序)。

  5. import random as randomModule 指定引用名。現在可以用randomModule來引用random 中的函數。

  6. 重複使用重複的結構,如for和while;遞歸使用選擇結構,如if和if/else。之間的差別 是,重複採用一個重複結構,而遞歸採用重複的函數調用。兩者都要進行終止測試:重複會 在循環繼續條件爲false時終止;遞歸在識別出基本條件是終止。由計數器控制的重複和遞 歸都是逐漸終止:重複會不斷改變一個計數器,直到計數器的值使循環繼續條件變爲false; 遞歸則不斷對原始問題進行簡化,直到抵達基本條件。重複和遞歸都可無休止地進行:如果 循環繼續檢測永遠都不能變成false,會發生無限循環;如果遞歸調用永遠不能將問題簡化 成基本條件,會發生無窮遞歸。

  7. 函數的默認參數設置需在def語句中定義。如:def test(aa =1,bb=2,cc=3):xxx 。

  8. 關鍵字參數可以不按位置順序出現在函數調用中。用keyword = value方式就可以了。

4.2. 良好的編程習慣

  1. 儘快熟悉核心python模塊提供的函數和類集合。

  2. 避免變量名遮蔽外層作用域中的名稱。爲此,要注意避免標識符與內建命名空間中的標 識符同名,並避免在程序中使用重複的標識符。

  3. 使用默認參數可簡化函數調用的編寫,但有的程序員認爲,顯式指定所有參數會使程序 更易讀。

4.3. 常見編程錯誤

  1. 用局部命名空間中的一個標識符遮蔽模塊或內建命名空間中的一個標識符,可能引起邏 輯錯誤。

  2. 默認參數必須全部靠右。省略非靠右的參數是語法錯誤。

4.4. 移植性提示

  1. 使用核心python模塊中的函數,通常可使用程序更易移植。

4.5. 軟件工程知識

  1. 避免重複別人的勞動。儘量使用標準庫模塊函數,不要寫新函數。這樣可加快程序開發 進度,並增強可靠性。因爲你所使用的是經良好設計和測試的代碼。

  2. 每個函數都應該只限執行單一的、良好定義的任務,函數名應清楚地描述那個任務。

  3. 如果實在想不出能準確表達函數作用的名稱,就表明函數可能執行了太多的分散任務, 通常,最好把這種函數分解成多個更小的函數。

  4. 程序應寫爲若干個小函數的集合。這樣使程序更易編寫、調試、維護和修改。

  5. 如函數需要大量的參數,表明它執行的任務可能過多。請考慮將函數分解成更小的函數, 令其執行單獨的任務。函數的def語句儘可能不超過一行。

  6. 採用遞歸方式能解決的任何問題也可採用重複方式(非遞歸方式)解決。如果遞歸方式 能夠更自然地反映問題,並使程序易於理解和調試,通常應該首選遞歸方式。通常,只需幾 行代碼就可完成一個遞歸方式,重複方式則相反,它需要大量的代碼來實現。選擇遞歸的另 一個原因是,重複方案也許不是很直觀。

  7. 採用清晰的、層次清楚的方式對程序進行“函數化”,有助於保證良好的軟件工程,但 性能上要付出一定代價。

4.6. 性能提示

  1. 不要試圖改寫現成的模塊函數使其更高效,因爲這些函數已非常完美了。

  2. 一般不要編寫會造成調用次數成指數級增加的“斐波拉契”式遞歸程序。

  3. 避免對性能要求高的時候使用遞歸。遞歸調用既費時、又耗內存。

  4. 一個由多個函數構成的程序,與一個沒有任何函數的一體式程序相比,會產生大量的函 數調用,這些函數調用會佔用大量的處理器時間和內存。但另一方面,一體式程序的編程、 測試、調試和維護都比較複雜。因此對程序進行函數化時要綜合考慮。保證能兼顧良好的性 能和軟件工程。

Chapter 5. 列表、元組和字典

Table of Contents

5.1. 知識點

5.1. 知識點

  1. Python支持3種基本序列數據類型:字符串(string),列表(list)和元組(tuple)。

  2. “映射”在其它語言中稱爲關聯數據或“哈希”,是用於存儲“鍵-值”對的數據結構。 python支持一種映射數據類型,字典。

  3. 創建序列:aString = "";aList = [];aTuple = ()。列表是可變序列,字符串和元組是不可 序列。

Chapter 6. 公共網關接口(CGI)入門

Table of Contents

6.1. 知識點

6.1. 知識點

  1. CGI可用於幾乎任何程序語言或腳本語言,比如C,PERL和PYTHON。

  2. 最常見的HTTP請求類型是GET AND POST。這些請求從WEB服務器獲取資源,並將 客戶表單數據發送給WEB服務器。get請求將表單內容作爲URL的一部份發送。大多數 WEB服務器將GET請求查詢字符串限制在1024個字符以內。如果查詢字符串超過這個限 制,就必須使用POST請求。POST請求中發送的數據不是URL的一部份,用戶看不到它 們。如果表單包含許多字段,那通常由POST請求進行提交。一些敏感的表單字段,如用戶 名和密碼,也通常使用這種請求類型來發送。GET請求的最簡單形式的格式爲GET /books/downloads.html HTTP/1.1。服務器收到請求後,會發送一個HTTP標頭,如 Content-type:text/html。表明MIME類型,然後服務器發送請求的HTML/XHTML文檔中文 本(DOWNLOADS.HTML)。

  3. web應用程序採用兩類腳本編程,服務器端和客戶端。CGI腳本是服務器端腳本的一個 例子,客戶端腳本的一個例子是javascript。

Chapter 7. 基於面向對象的編程

7.1. 知識點

  1. 在過程式語言中,基本編程單元是“函數”,在面嚮對象語言中,基本編程單元是“類”, 最終要通過它來實例化(即創建)對象。

  2. __init__方法是類的“構造函數”方法。每次創建類的一個對象時,都會執行它的構造 函數。它會初始化對象屬性,並返回None。

  3. 包括構造函數在內的所有方法至少要指定一個參數。該參數代表要調用其方法的類的對 象。人們常把這個參數稱爲“類實例對象”。但由於這術語容易混淆,所以我們將任何方法 的第一個參數都稱爲“對象引用參數”,或簡稱“對象引用”。方法必須通過對象引用來訪問 從屬於類的屬性以及其它方法。按照約定,對象引用參數稱爲self。

  4. 類的特殊屬性。

    • __bases__ 包含基類的一個元組,類可以從這些基類直接繼承。如果類不從其他類繼承, 元組就會爲空。

    • __dict__ 與類的命名空間對應的一個字典。其中每個鍵-值對都代表在命名空間中的一個標 識符及其值。

    • __doc__ 類的文檔字符串。如果類沒有指定文檔化字符串,值爲None。

    • __module__ 包含模塊(文件)名的一個字符串,類定義於這個模塊中。

    • __name__ 包含類名的一個字符串。

  5. 在C++和java等程序語言中,類可明確指出類的客戶能訪問哪些屬性或方法。這些屬性 或方法被認爲是“公共”的。不能由類的客戶訪問的屬性和方法則被認爲是私有的。在python 中,對象的屬性是肯定能訪問的--沒有辦法阻止其它代碼訪問數據。然而,python提供一種 特別的機制來防止任意訪問數據。在屬性名附加雙下劃線前綴。python解釋器會對屬性執行 “名稱重整”。如self.__hour,python會創建一個_Classname__hour的屬性。但它一樣是可 訪問的,只是名字變了。

  6. 構造函數也可以定義默認參數,從而在客戶沒有指定參數的前提下,爲對象屬性指定初 始值。還可以定義關鍵字參數。

  7. 析構函數__del__是構造函數__init__的相反,用於執行“終止清理”,然後由解釋器回收 對象的內存,使內存能被重用。析構函數通常只指定self參數,並返回None。

  8. 類的每個對象都擁有在構造函數中創建的所有屬性的拷貝。特定情況下,類的所有對象 只能共享屬性的一個拷貝。爲此要使用“類屬性”。它是“類範圍”的信息(也就是說,它 是類的一個屬性,而非類的特定對象屬性)。它可節省空間和時間,提高性能。爲訪問類屬 性,只需爲屬性名附加類名前綴,再加一個小數點即可。(Classname.xxx)

7.2. 良好的編程習慣

  1. 文檔化字符串習慣上是一個三引號字符串。這樣可以在不改變引號樣式的前提下,擴展 一個程序的文檔(例如添加更多的行)。

  2. 儘可包含文檔化字符串,使程序更有條理。

  3. 將所有方法的第一個參數都命名爲self。始終遵循這一命名約定,可確保不同程序員編 寫的python程序是一致的。

  4. 屬性名以單下劃線開頭,雖然在python語法中沒有特殊的含義,但單下劃線是python 程序員使用類時約定使用的符號,表明程序員不希望類的用戶直接訪問屬性。以單劃線開頭 的屬性揭示一個類的接口的相關信息。類如果定義了此類屬性,它的客戶就只能通過類提供 的訪問方法來訪問並修改屬性值。如果不是這樣做,通常會導致程序執行期間出現不可預料 的錯誤。

7.3. 常見編程錯誤

  1. 忘記將對象引用(通常是self參數)設爲方法定義中的第一個參數,會導致該方法在運 行時被調用時,造成嚴重邏輯錯誤。

  2. 直接訪問對象的屬性可能導致數據進入不一致狀態。一個辦法是讓類提供“訪問方法”, 通過一種得到精心控制的方式來讀寫類數據。

  3. 如果忘記在方法內部通過對象引用(通常稱爲self)來訪問由對象的類定義的另一個方 法,就會導致嚴重的運行時錯誤或者邏輯錯誤。如全局命名空間包含的一個函數與類的某個 方法同名,就會產生邏輯錯誤。此時,如果忘記通過對象引用來訪問方法名,實際會調用全 局函數。

7.4. 測試和調試提示

  1. 即使提供了訪問方法,也無法自動確保數據完整性,程序員必須提供有效性驗證。

7.5. 軟件工程知識

  1. 本書的中心思想是“重用,重用,再重用”。我們的重點是“創建寶貴的類”,創造有價 值的“軟件資產”。

  2. 先初始化對象,再讓客戶代碼調用對象的方法。不能依賴客戶代碼正確初始化對象。

  3. 利用訪問方法控制對屬性的訪問(尤其是寫訪問)有助於確保數據完整性。

  4. python的類和模塊化機利於程序的獨立實現。如果代碼所用的一個類的實現發生了改變, 這段代碼是無需更改的。

  5. 並不是所有的方法都要作爲類的接口的一部份。有的方法是類的其它方法的一種實用方 法,不準備供類的客戶使用。

  6. 將客戶不應該訪問的任何數據設爲私有。

  7. 如果類的一個方法提供了構造函數(或其他方法)需要的全部或部份功能,請從構造函 數(或其他方法)中調用那個方法。這樣可簡化代碼的維護,並減少代碼的實現改變後出錯 的可能。一個通用規則是:避免重複代碼。

  8. 合成是軟件重用的一種形式,即類的成員引用了其他類的對象。

  9. 在類成員引用了另一個類的對象的前提下,使那個成員對象能被公共訪問,不但沒有違 反封裝性,而且還可隱藏那個成員對象的私有成員

7.6. 性能提示

  1. 鏈式比較表達式(0 <= a < 10)的效率比非鏈式表達式( a >= 0 and a < 10)更高,因爲鏈式比較表達式中的每個條件都只執行一次。

  2. 如果數據的一個拷貝已經夠用,請用類屬性以節省空間。

Chapter 8. 自定義類

8.1. 知識點

  1. 運算符+在python中具有多種用途,比如整數加法和字符串連接。這就是運算符重載的 一個例子。它會在不同的背景下執行最恰當的運算。

  2. python 類可定義特殊方法__str__,爲類的對象提供一個不正式的(即人們更容易理解的)字符串表示。如果類的客戶程序包含以下語句:print objectofclass ,那麼python會調用對象的__str__方法,並輸出那個方法所返回的字符串。如: print test 就會執行以下語句: print test.__str__

  3. 前面介紹客戶訪問對象屬性的方法有兩種,一種是客戶可直接訪問屬性(使用點訪問運 算符):另外,也可通過客戶定義的訪問方法來訪問屬性。這一節討論另外一種技術---定義 特殊方法,自定義直接屬性訪問的行爲。python提供了一系列特殊方法,類可定義這此方法, 以控制點訪問運算符操縱類對象的方式。如:

    • __delattr__ 客戶刪除一個屬性時執行(例如 del anObject.attribute)

    • __getattr__ 客戶訪問一個屬性名,但在對象__dict__屬性中找不到這個名稱時執行(例如anObject.unfoundName)

    • __setattr__ 客戶將值指派給對象的屬性時執行(例如 anObject.attribute = value)

  4. 在多數python運算符和增量賦值符號都能重載,有兩個運算符不能重載,即{}和lambda。

  5. 如果重載一元運算符(如 + - * ),會自動重載與運算符對應的增量賦值語句。

8.2. 良好的編程習慣

  1. 如有必要,請爲你創建的模塊提供test函數,這些函數可確保模塊正常工作,而且能通 過演示模塊的工作方式,向客戶提供額外的信息。如以下語句:


    if __name__ == "__main__":
     test()

    如果另一個程序導入模塊,__name__的值就會是模塊名,而test函數不會執行。如果模塊 作爲單獨的程序執行,__name__的值是“__main__”,test函數就會執行。

8.3. 常見編程錯誤

  1. 從__str__方法返回非字符串值是嚴重的運行時錯誤。

  2. 在__setattr__方法中,通過點訪問運算符爲對象屬性指派值會造成無窮遞歸。相反,應 使用對象的__dic__屬性。見chapter8.2.py

8.4. 軟件工程知識

  1. 對於大型系統,如果需要嚴格的數據訪問,設計者就應使用__getattr__和__setattr__來確 保數據的完整性。大型系統的開發者如果使用python2.2,可藉助於Properties這種更高效技 術來利用__getattr__ 和 __setattr__。

8.5. 性能提示

  1. 有時,更好的方法是重載運算符的增量賦值版本,以便能夠“當場”執行操作(也就是 說,不通過新建對象來佔用額外的內存)。

Chapter 9. 面向對象編程:繼承

9.1. 知識點

  1. 創建新類時,程序員不必編寫全新的屬性和方法,只需指明新類繼承以前定義好的“基 類”的屬性和方法。新類稱爲“派生類”。每個派生類本身也可以是未來一些派生類的基類。 在“單一繼承”中,類只從一個基類派生;但在“多重繼承”中,派生類要從多個基類繼承。

  2. 覆蓋後的派生類構造函數通常會調用基類構造函數,從而先初始化基類屬性,再初始化 派生類屬性。

  3. 派生類可覆蓋一個基類方法,做法是採取相同的名稱提供那個方法的一個新版本。

  4. 通過繼承,可自定義現有軟件。首先繼承現有類的屬性和行爲,再添加新的屬性或行爲, 或覆蓋基類行爲,從而對類進行自定義,使之符合我們的要求。

  5. 基類指的是公共特性---從基類繼承的所有類者得到了基類的功能。在面向對象設計過程 中,設計者要找出公共特性,並對其進行歸納,以構成合理的基類。然後,派生類自定義基 類功能之外的功能。

  6. 綁定方法調用是通過一個對象來訪問方法名,如anObject.method()。非綁定方法調用需 要通過類名來訪問方法,並專門傳遞一個對象引用。如Point.__init__(self,x,y),self(Cricle 類的一個對象)作爲對象引用傳遞。

  7. 我們在把類想像成一種類型時,就假定要創建那個類型的對象。但是,偶然也需要定義 一些類(程序員永遠不打算創建它的任何對象)。這樣的類稱爲“抽象類”。由於它們在繼承 層次結構中作爲基類使用,所以通常把它們稱爲“抽象基類”。我們不爲抽象類創建對象。 它惟一的用途是提供一個合適的基類,以便其他類從中繼承接口,偶爾也繼承它的實現,從 中實際創建的類稱爲“具體類”。

  8. python支持“多態性”,通過繼承聯繫在一起的各個不同類的對象可針對同樣的消息(方 法調用)做出不同的響應。發送給多個類型的對象的相同的消息會呈現出“多種型態”--- 這正是“多態性”一詞的來歷。例如,假定矩形類從四邊形類派生,那麼矩形屬於四邊形的 一個更具體的版本,能對四邊形類的一個對象執行的操作(比臺計算周長或面積),也能對 矩形類的一個對象執行。

  9. 現在討論一下多態性的應用。一個屏幕管理器需要顯示不同類的大量對象,其中包括軟 件寫好之後再加入系統的新類型。系統要能顯示各種幾何形狀(基類是shape),例如正方形, 圓形,三角形,矩形,點,線等等(都從基類shape類派生)。屏幕管理器使用基類引用來 管理所有需要顯示的類。繪製任何對象時(無論它位於繼承層次結構的哪一級),屏幕管理 器都只是向對象發送一條draw消息。draw方法在每個派生類中都能被覆蓋。shape類的每 個對象都知道怎樣繪製自已。屏幕管理器不需要關心每個對象的類型,也不需要關心以前是 否見過這一種類型的對象---它只需要告訴每個對象draw自已就可以了。

  10. 多態性尤其適合實現分層式軟件系統。例如在操作系統中,每類物理設備的工作方式 都是不同的,但無論如何,從設備“讀”和“寫”數據的命令肯定能統一。發送給設備驅動 程序對象的“寫”消息需要在那個設備驅動程序的背景下進行專門解釋,這具體取決於設備 驅動程序怎樣操縱特定類型的設備。但是,就“寫”調用本身來說,它和向系統中其它任何 設備的“寫入”操作沒有任何區別---都是將一些數量的字節從內存中放到設備中。面向對象 的操作系統可使用一個抽象基類提供適用於所有設備驅動程序的一個接口。然後,通過從這 個抽象類繼承,派生類可採取類似的方式工作。設備驅動程序具有的功能(即接口)作爲抽 象基類中的方法提供。在派生類中,對這些方法進行了具體的實現,它們與特定類型的設備 驅動程序是相對應的。

  11. 通過多態性編程,程序可遍歷一個容器,比如由類層次結構各個級別上的對象構成的 一個列表。只需發關一條消息就能執行列表中的所有對象。

  12. 在python2.2之前的版本中,類和類型是兩種截然不同的編程元素。所以程序員不能從 內建類型繼承,不便使用列表,字典和其它對象提供的高級數據處理能力。自python2.2起, 類的本質與行爲都發生了變化,消除了類型和類的差異。在將來的所有2.X版本中,程序員 可區分兩種不同的類,即所謂的“經典類”(它的行爲方式與本章前面以及前兩章所展示的 類相同)以及“新類”(它們具有新的行爲)。object類型用於定義新類型。直接或間接繼承 於object的所有類都具有爲新類定義的行爲。

  13. 所有類都可定義“靜態方法”。靜態方法可由一個客戶調用,即使不存在類的任何對象。 通常,靜態方法是類的一個實用方法,不需要類的一個對象就能執行。一個類如果想把方法 指定爲靜態的,就必須向內建函數staticmethod傳遞方法的名稱。再爲函數調用返回的值綁 定一個名稱。靜態方法不將self指定爲第一個參數。這樣一來,即使沒有類的對象,也能調 用靜態方法。

  14. 靜態方法在java等語言中至關重要,這些語言要求程序員將所有代碼都放入一個類定 義中。使用這些語言,程序員經常要定義只包含表態實用方法的類。隨後,類的客戶可調用 靜態方法,這和python程序調用模塊裏定義的函數的方式非常相似。在python中,靜態方 法允許程序員更準確地定義一個類接口。如果類的方法不需要類的對象即可執行其的任務, 程序員就可將這個方法指定爲靜態的。

  15. 從基類object繼承的類也可定義__getattribute__方法,每次訪問屬性時都會執行它。 派生類中的__getattribute__方法必須調用方法的基類版本,才能獲取屬性值,這是由於假 如通過對象的__dict__來訪問屬性值,會造成對__getattribute__的另一次調用。

  16. python2.2允許新類定義一個__slots__屬性,它可列出只允許類的對象擁有的屬性。

9.2. 常見編程錯誤

  1. 如果派生類的已覆蓋構造函數需要調用基類構造函數來初始化基類的成員,派生類構造 函數就必須顯式地調用基類構造函數。不從派生類中調用基類構造函數是邏輯錯誤。

  2. 基類方法在派生類中被覆蓋後,經常要讓派生類版本調用基類版本,並執行一些附加的 操作。在這種情況下,如果不使用基類名稱來引用基類方法(也就是爲基類方法附加基類名 稱和一個點前綴),會造成無窮遞歸,因爲派生類方法實際調用的是自身。這最終導致系統 死機。

  3. 不將非綁定方法調用的第一個參數指定爲對象引用是邏輯錯誤。

  4. 要保證正確的屬性訪問,__getattribute__方法的一個派生類版本應該調用該方法的基類 版本。如試圖通過訪問對象的__dict__來返回屬性值,會造成無窮遞歸。

9.3. 軟件工程知識

  1. 和其他任何類一樣,派生類不一定要定義構造函數。如果派生類沒有定義構造函數,一 旦客戶創建類的一個對象,就會創建類的基類構造函數。

  2. 創建派生類不會影響其基類源代碼;基類的完整性通過繼承獲得了保持。

  3. 在面向對象系統中,類通常是緊密聯繫的。因此,請歸納出公共屬性和行爲,將其放入 一個基類,然後通過繼承來生成派生類。

  4. 修改基類時,只要到基類的接口保持不變,就不必修改派生類。

  5. 通過多態性,程序員可關注於公共特性,讓執行時環境去關心具體特性。程序員可在不 知道那些對象是什麼類型的情況下,指示大量對象採取恰當的行動。

  6. 多態性增強了擴展性:軟件如果要調用多態行爲,編寫時便不用關心要向其發送消息的 對象類型。因此可在不修改基本系統的前提下,自由添加新類型對象,令其響應現有的消息。

  7. 抽象類爲類層次結構的各個成員定義了一個接口。抽象類包含的方法將在派生類中定 義。在多態性的幫助下,層次結構中的所有方法都可使用同一個接口。

  8. 如果新類定義了__slots__屬性,但類的構造函數沒有初始化屬性值,那麼一旦創建該類 的一個對象,python就會將None值指派給__slots__中的每個屬性。

  9. 派生類會繼承它的基類的__slots__屬性。然而,如果不希望程序爲派生類的對象添加屬 性,派生類就必須定義自已的__slots__屬性。在派生類的__slots__中,只包含允許的派生類 屬性名,但客戶仍可爲派生類的直接/間接基類所指定的屬性賦值。

9.4. 性能提示

  1. 如果通過繼承生成的類超過所需,可能會無謂地浪費內存和處理器資源。因此,請從最 能滿足你需求的類繼承。

Chapter 10. 圖形用戶界面組件(一)

10.1. 知識點

  1. GUI是事件驅動的,也就是說,一旦用戶與GUI交互,GUI組件就會生成”事件“(動 作)。常見交互包括移動鼠標、單擊鼠標按鈕、在文字段輸入、從菜單選擇一個選項以及關 閉一個窗口等等。

  2. Tkinter模塊中的每一個Tk GUI組件都是從Widget類繼承的一個類。所有從Widget派 生的類都具有公共的屬性及行爲。

  3. Entry(輸入)組件是一種特殊的屏幕區域,用戶可以在其中輸入文本,也可顯示一行 文本。

  4. pack方法規定了組件應該如何放到它的“父”中,以及應該放到什麼地方。

  5. 如果程序員不指定名稱,Tkinter會爲每個組件分配一個不重複的名稱。要想獲得一個組 件的完整名稱,將組件對象傳給str函數即可。

  6. bind方法將一個<Return>事件同text1組件關聯在一起。一旦用戶按下回車鍵,<Return> 事件就會發生。bind方法要取得兩個參數,一個是事件類型(事件格式),一個是要與事件綁定的 那個方法的名稱。

  7. insert方法在Entry組件中寫入文本,它要取得兩個參數:文本插入的位置和包含插入文本 的一個字符串。如果第一個參數是INSERT,文本就會在當前光標位置插入。如果是END,則在末尾插 入,如:insert(END,text)。不可用delete(start,finish)方法從Entry組件中刪除文本。在 Entry組件中,第一個位置編號是0,所以如果delete(0,END)則是刪除所有文本。

  8. Widget 的winfo_name方法返回組件名,Entry的get方法返回Entry的內容。showinfo() 函數顯示一個標記爲"message"的對話框。

  9. Button的command關鍵字參數指定了在用戶選擇按鈕之後要執行的事件處理程序。

  10. pack的參數,side指明組件要靠在容器的哪一邊,可選擇TOP(DEFAULT),BOTTOM,LEFT 和 RIGHT。fill指定組件在容器中應占據的空間(在指定的方向上),可設爲NONE(DEFAULT),X,Y 或 BOTH。expand設置組件只否自動擴展,填充容器中任何額外的空間。padx and pady可圍繞組件插 入空白填充。pack_forget方法可從容器中刪除一個pack好的組件。

  11. Grid佈局管理器將容器分割爲一個網格,使組件能以行、列形式放置。組件在網格中的位置 由其row和column值決定;網格中的每個單元格都可包含一個組件。行、列編號從0開始。還可以調 用rowconfigure和columnconfigure方法來設置行和列。

  12. place佈局管理器允許設置一個GUI組件的位置和大小---既可以是絕對值,也可是與另一個 組件的相對位置和大小。它較複雜在這裏不詳細討論。

10.2. 良好的編程習慣

  1. 爲每個Button都單獨定義一個回調方法,這有助於避免混淆,確保實現目標行爲,並簡化GUI 的調試。

  2. 妥善選擇佈局管理器可使GUI編程更容易。編程之前,先畫好設計圖,再選擇最合適的佈局管理 器。可用的佈局管理器有三種,Pack(按添加的順序放置組件),Grid(以行、列形式排列組件), Place(允許程序員指定組件和窗口的大小和位置)。

10.3. 常見編程錯誤

  1. 在同一個容器中使用多種類型的佈局管理器,一旦Tkinter試圖協調不同管理器的不同需求,就 會導致應用程序死機。

  2. pack方法按定義順序將各組件放到一個容器中,所以,如果錯誤定義順序,會導致不可預料的結果, 相反,如果放置組件時,採用了爲side,expand,fill,padx,pady指派值的方式,就可以忽略其定義順 序,保證獲得所需結果。

  3. 有時可能指定重疊組件。代碼中先出現的組件會被最近添加的組件遮住。

10.4. 界面知識

  1. 通過改變按鈕的顯示效果,可提供一種視覺線索,讓用戶知道自已已經選中了按鈕,而且發生了 相應的動作。

Chapter 11. 圖形用戶界面組件(二)

11.1. 知識點

  1. python巨元件(Python Megawidgets,Pmw)提供高級的GUI組件,它是基於Tkinter模塊提供 的較少的組件開發的。每個Pmw組件都合併了一個或多個Tkinter組件,從而生成更有用,更復雜的組件。

  2. ScrolledListBox組件叫滾動列表框,是由於列表中的項目個數較多,以至於列表無法在屏幕上顯 示時的解決方案。

  3. ScrolledText組件其實是能滾動的Tkinter Text組件。

  4. MenuBar組件用於創建菜單。

  5. Canvas組件用於顯示文本、圖像、線條、和形狀。

  6. Scale(滑桿)組件允許用戶從一系列整數值中選擇。

  7. 其它GUI工具包有:PyGTK爲Gimp TooKit組件提供了面向對象的接口。GTK是一個高級的組件集,主要 在X WINDOWS系統中使用。PyGTK是GTK+的一部份,後者也是一個Python工具包,用於創建圖形用戶界面。另 一個流行的GUI工具包是wxPython,它是一個Python擴展模塊,提供了對wxWindows(一個用C++寫成GUI庫) 訪問途徑。PyOpenGL爲OpenGL庫提供了一個python編程接口。OpenGL是開發交互式二維和三維圖形應用程序 的流行方案,

11.2. 測試和調試提示

  1. 程序使用Pmw時,如果不調用Pmw.initialise,就不能使用Pmw模塊的完整功能。

11.3. 界面知識

  1. 菜單項按照它們添加的順序出現,因此,務必按正確順序添加。通常按添加順序從左到右排列。

Chapter 12. 異常處理

12.1. 知識點

  1. Python中異常處理的樣式及細節基於Modula-3語言,類似於c#和java。

  2. Python使用try語句實現異常處理。try語句包圍着可能引發異常的語句。try語句以關鍵字try 開頭,後續一個冒號和一個可能在其中引發異常的代碼塊。try可指定一個或多個except子句,它們緊接 在try塊後,每個except子句指定了零個或多上異常類名,它們代表要由except子句處理的異常類型。 可在except子句中指定一個標識答,程序用它引用被捕獲的異常對象。處理程序利用標識符從異常對象 獲取與異常有關的信息。如果except子句中沒有指定異常類型,則會捕捉所有類型的異常。在最後一個 except子句之後,可選擇性地添加一個else子句。如果try塊中的代碼沒有引發異常,就會執行else 子句中的代碼。如果try語句中沒有指定except子句,那不必須包含一個finally子句,該子句肯定會執 行,無論是否發生異常。

  3. 異常是一些類的對象,這些類都是從Exception類繼承的。如果有一個try塊中發生異常,這個塊會 立即終止,程序控制權會移交給try塊後的第一個except處理程序。解釋器爲了確定相匹配的except,需 要將引發的異常類型與每個except類型比較,直到匹配爲止。如果類型完全一致,或引發的異常類型是 except處理程序的異常類型的一個派生類,就表明發生了匹配。

  4. 如果try塊中沒有發生異常,解釋器將忽略用於try語句的異常處理程序,並執行try語句的else子句 (如果有的話),如果沒有發生異常,或某個except子句處理了這個異常,程序會從try語句之後的下一個 語句恢復執行。如果某個語句發生了異常,但該語句不在try塊中,而是在一個函數中,那麼包含這個語句的 函數會立即終止,解釋器會試圖在(發出)調用(的)代碼中查找一個封閉的try語句,這個過程稱爲“堆棧 輾轉開解”。

  5. python使用“異常處理的終止模型”,因爲引發異常的try塊會在異常發生後立即“過期”。有的語言 使用“異常處理的恢復模型”,完成異常處理後,控制權會返回異常引發點,並從那個位置恢復執行。

  6. 儘管可在try塊中包含任意語句,但通常只包含可能引發異常的語句。在else塊中,則放置不會引發異 常的、而且只有在相應的try塊中沒有發生異常的前提下才應該執行的語句。

  7. python的Exception異常類是一種層次結構,從Exception繼承的4個類包括SystemExit,StopIteration, Warning以及StandardError。

  8. python中不存在內存泄漏問題,解釋器會自動進行“垃圾回收”。但解釋器不能完全清除內存泄漏。只 要存在對一個對象的引用,解釋器就會對其進行垃圾回收。所以,如果程序員偶然保留了不需要的對象引用, 仍會出現內存泄漏。

  9. python異常處理機制提供了finally子句,只要程序控制進入相應的try塊,就必然會執行這個finally 子句(無論try成功執行與否),所以finally是放置資源回收代碼的理想地點。如果try塊執行成功,則finally 會在try塊終止後立即執行,如果try塊發生異常,則會在導致異常的那一行之後,立即執行finally子句。

12.2. 良好的編程習慣

  1. 使每類問題都與一個命名得體的異常類關聯,使程序結構清晰易讀。

  2. 在創建程序員自定義異常類之前,應分析python層次結構中現有有異常類,看是否有可滿足自已需要的類, 只有在程序需捕捉和處理與現有異常類型不同的新異常時,才定義新異常類。

12.3. 常見編程錯誤

  1. 退出程序可能導致資源(比如文件或網絡連接)無法由其它程序使用。這稱爲“資源泄漏”。

  2. 在try語句中同時包括except和finally子句是語法錯誤。可接受的組合形式中有:try/except, try/except/else以及try/finally。

  3. 每個try塊與其第一個except處理程序之間、兩個except處理程序之間、最後一個except處理程 序與else子句之間或者try塊和finally子句之間,不允許出現任何語句,否則是語法錯誤。

  4. 如果try語句既不包括finally語句,也不包括except語句,那就屬於語法錯誤。如果try語句不包括 except子句,那麼必須包括一個finally語句,如果try語句不包括finally語句,那麼至少包括一個except子句。

  5. 在finally中引發異常是非常危險的,執行finally子句時,如果一個未被捕獲的異常正在等候處理,而 finally又引發一個新的、未被該塊捕獲的異常,那麼第一個異常就會丟失,新異常則傳遞給下一個封閉的try語句。

12.4. 測試和調試提示

  1. 在finally塊中,必須將可能引發異常的代碼封閉到一個try語句中,這樣可避免丟失未捕捉的、 在finally塊執行之前發生的異常。

  2. traceback展示了自異常發生之時起,一個完整的函數調用堆棧。這樣一來,程序員就可查看導致 異常的一系列函數調用。traceback中的信息包括輾轉開解的函數名稱:在其中定義了函數的那個文件的 名稱以及程序遇到錯誤時的行號。traceback中的最後一個行號是“引發點”(即初始異常引發的位置)。 在它之前的一系列行號則是在traceback中每個函數的調用位置。

  3. 閱讀traceback報告時,請按從下到上的順序,先讀錯誤消息。然後,向上閱讀traceback剩餘部 份,查找報告中的第一個行號。通常,這就是導致異常的位置。

12.5. 軟件工程知識

  1. 從設計階段初期,就要將異常處理策略考慮到系統中。系統實現後再添加有效的異常處理是很難的。

  2. 過去,程序員用多種技術來實現錯誤處理代碼。異常處理則提供了一種統一的機制來處理錯誤, 因此,當許多程序員從事同一個大型項目時,相互之間可輕鬆理解對方的錯誤處理代碼。

  3. 通常不要將異常顯式地應用於常規控制流程(雖然也可這樣做),否則會因爲較難跟蹤隨後發生 的大量異常,導致程序變得難以閱讀和維護。

  4. 要精心選擇放入try塊中的代碼,其中應有好幾個語句都可能引發異常。儘量避免爲可能引發異常 的每個語句都單獨使用一個try語句。但是,要想確保正確的異常處理,每個try語句都應儘可能封裝一 個足夠小的代碼區,這樣一來,一旦發生異常,就可確切把握當時的背景,使except處理程序能正確地 處理異常。如果一個try塊中的多個語句都可以引發相同的異常類型,就必須用多個try語句確定每個異 常的背景。

  5. 在一個except子句中,可以指定多個異常,只需在圓括號中使用由逗號分隔的一個異常名稱序列 即可,要用except子句指定我個異常,這些異常應以某種形式相互關聯(如都是因算術計算錯誤而引發 的異常)。將相關的異常分爲一組,再爲每一組單獨使用一個except子句。

  6. 引發異常的代碼要先釋放代碼中獲取的任何資源,再引發異常。

  7. 如果try語句指定了一個finally子句,那麼即使try塊由一個return語句終止,finally子句的 代碼也會。執行完成後,才輪到通過return返回調用代碼。

12.6. 性能提示

  1. 如果沒有發生異常,異常處理代碼對性能的影響極微(或者說根本沒有),所以,相較於在程序邏 輯中混合了錯誤處理邏輯的程序,實現了異常處理的程序效率更高。

Chapter 13. 字符串處理和正則表達式

13.1. 知識點

  1. python提供了find 和 index等方法來查找字符串。

  2. 字符串的count方法返回子字符串在字符串或字符串分片中出現的次數,如果方法沒有找到指定的子字符 串,方法返回0。

  3. find方法返回子字符串出現時的最低索引位置。如果找不到,則返回-1.index方法類似於find方法。只 是假如字符串中不包含子字符串,方法會引發ValueErro異常。這樣,程序就可以捕捉該異常,並進行處理。

  4. startswith方法用於判斷一個字符串是否以指定子字符串開始,如果是則返回1。

  5. endswith方法用於判斷一個字符串是否以指定子字符串結尾,如果是則返回1。

  6. rfind方法從字符串尾部開始搜索,返回首次出現子字符串的索引位置。如果沒有發現則返回-1。

  7. rindex方法則返回字符串出現的最高索引位置,沒有發現子串就引發ValueError異常。我們的程序就可 捕捉該異常,以處理字符串中不包含指定字符串的情況。

  8. replace方法可取得兩個子字符串,並在文檔中搜索第一個字符串,把它替換成第二個字符串。它還可取得 第三個參數,指定最大的替換次數。如只允許替換3次,其它的忽略。

  9. split和join方法用於分解和連接字符串。

  10. 所謂正則表達式,是一種"文本模式"(Text Pattern),用於查找與模式相匹配的子字符串。

  11. 原始字符串(Raw String)是在字符串之前加上字符前綴r後創建的一個字符串,在原始字符串中,Python 不會把反斜槓/視爲轉義字符,而是理解爲一個/符號。

  12. re.sub函數取得3個參數,第一個指定模式,第二個指定子字符串,第三個指定字符串。在第三個參數指定的 字符串中,與模式匹配的每個子字符串都會被替換成第二個參數指定的子字符串。

  13. re.split函數取得兩個參數,第一個參數是正則表達式,用模式來描述定界符。函數在定界符處對第二個參數 進行分解,返回一個標記列表。

13.2. 良好的編程習慣

  1. 如果進行簡單的處理,請使用字符串方法。這樣可避免複雜的正則表達式所帶來的錯誤,並保證程序的可讀性。

  2. 正則表達式的模式字符串中常常包含反斜槓字符。使用原始字符串來創建模式,可避免對其中每個反斜槓進行 轉義的必要,使模式字符串更容易理解。

13.3. 性能提示

  1. 構建複雜字符串時,效率更高的一種做法是將不同組件包括到一個列表中,再用join方法彙總字符串,而不 要使用連接運算符(+)。

Chapter 14. 文件處理和序列化

14.1. 知識點

  1. python程序開始執行時,會創建3個文件流,包括sys.stdin(標準輸入流),sys.stdout(標準輸出流), 以及sys.stderr(標準錯誤流)。這些流在程序和特定文件/設備之間建立溝通渠道。

  2. 文件打開模式指出文件打開後要進行讀取,寫入,還是兩者兼而有之。各種模式說明如下:

    a

    所有輸出都寫到文件尾,如果指定的文件不存在,就創建一個。

    r

    打開文件以便輸入。如果文件不存在,就引發IOError異常。

    r+

    打開文件以便輸入和輸出。如果文件不存在,就引發IOError異常。

    w

    打開文件以便輸出。如果文件存在,就刪除其中所有數據。如果不存在就創建一個。

    w+

    打開文件以便輸入和輸出。如果文件存在,就刪除其中所有數據。如果不存在就創建一個。 不有一中是以上所有模式後加b,如ab,rb,表示打開二進制文件(也就是非文本形式)輸入或輸出,注意,只是windows和macintosh平臺支持這些模式。

  3. options[]()結構表示,把()裏的參數傳給[]裏的函數處理。這樣的結構可避免使用冗長的if/else語句來判斷用戶菜單選項。

  4. 序列化(Serialization)是指將用戶自定義類等複雜對象類型轉換成字節集,以便存儲或通過網絡傳輸。序列化也稱爲“平坦化”(Flattening),或者“編組”(Marshalling),python用pickle和cPickle模塊來執行序列化。

14.2. 良好編程習慣

  1. 如果不希望文件內容被修改,應以只讀模式打開文件(使用"r"),防止因爲不慎而修改文件內容。這是"最小權 限原則”的一個例子。

  2. python2.2 中允許程序員在for語句中使用文件對象。如for record in file: ,這樣可每次讀取file的一行,並將該行指派給recode。程序可立即處理那一行。與通過readlines方法讀取一個大文件的內容相比,用這種方 法遍歷文件中的各行顯得更高效。因爲使用readlines,必須先將整個文件都讀入內存,然後才能對文件內容進行處理。

  3. 文 件對象提供seek方法,可用它重新定位“文件位置指針”,如file.seek(0,0),會將“文件位置指針”重新定 位至文件頭。seek的第一個參數是“偏移量”,它是一個整數值,以字節數的形式指定了文件中的一個位置(要根據文件的“seek方向”。第二個參數是可 選的,指定偏移的開始位置,或稱"seek方向"。seek方向可設爲0,表明相對於文件頭進行定位;也可設爲1,相對於當前位置進行定位;或設爲2,表 明相對於文件尾進行定位。

  4. python提供shelve模塊模擬隨機訪問文件。shelve對象具有一個字典接口--也就是訪問記錄信息的記錄鍵。

14.3. 常見編程錯誤

  1. 在用戶希望保留原始文件內容的前提下,用"w"模式打開文件以進行輸出,是一個邏輯錯誤,因爲文件內容會在用 戶未得到警告的情況下刪除。

14.4. 性能提示

  1. 一旦程序不再需要引用文件,就顯式地關閉文件,這樣可節省程序所用的資源,還有助於改善程序的可讀性。

  2. cPackle模塊的執行效率高於pickle模塊,因爲cPickle是用c實現的,並被編譯成每種平臺上的原生機器語言。

Chapter 15. 可擴展標記語言(XML)

15.1. 知識點

  1. XML(Extensible Markup Language,可擴展標記語言)於1996年由萬維網協分(W3C)下屬的“XML工作組”開發成功。它是一種可移植的、獲得普遍支持的開放式技術 (也就是一種非專利技術),它用於對數據進行描述。XML很快就成爲在應用程序之間交換數據的一個標準。

  2. XML是區分大小寫的,爲處理XML文檔,需要一種名爲“XML解析器”的程序。解析器負責檢查XML文檔語法,並使XML文檔的數據能提供應用程序使用。

  3. 在XML中也有命名空間的概念,就象python中的一樣。以防止程序員自定義標識符與類庫中的標識符出現“命名衝突”。命名空間的前綴標識元素屬於哪個命名空間,從而對元素進行區分,如:

    <deitel:publication>
    Python How to Program
    </deitel:publication>

  4. 爲 確保命名空間是獨一無二的,文檔作者必須提供唯一的URI,一種常見的做法是將URL作爲URI使用,因爲URL中使用的域名肯定是沒有重複的。注意,解 析器永遠不會去訪問這些URL,它們只是一系列用於區分名稱的字符。並不引用實際的網頁,也不一定需要具有正確的形式。

  5. 爲了避免爲每個標記名都附加命名空間前綴,文檔作者可用“默認命名空間”。即不用<text:directory xmlns:text = "xxx">,而用<directory xmlns = "xxx">的形式來創建和使用命名空間。

  6. 盡 管XML是文本文件,但通過順序文件訪問技術從中獲取數據,顯得既不實際,也沒效率,尤其是在其中的數據需要動態添加和刪除的時候。所以我們需要一種解析 器,對XML文檔進行解析,有些XML解析器能將文檔數據解析成樹的形式存儲在內存中,這個層次化的樹結構被稱爲“文檔對象模型(DOM)”樹,能夠創建 這類結構的XML解析器稱爲“DOM”解析器。

  7. SAX(Simple API for XML)是解析XML文檔的另一種方法,使用的是一個“基於事件的模型”。SAX處理文檔時,會生成名爲“事件”的通知信息。軟件程序可“偵聽”這些事件,以便從文檔獲取數據。

  8. DTD(文檔類型定義)和Schema(模式)是指定XML文檔結構的文檔(包含哪些元素是允許的,一個元素可以有什麼屬性等等)。提供一個DTD或Schema之後,解析器就會讀取它,用於驗證XML文檔的結構是否合法。

  9. DTD使用EBNF(Extended Backus-Naur Form)語法來描述XML文檔內容。

  10. 一個商務信函DTD文檔的例子(節選)

    <!-- DTD Document for letter.xml -->
    <!ELEMENT letter ( contact+, salutation, paragraph+) >
    <!ELEMENT contact ( name, address1, address2, city, state, zip, phone, flag ) >
    ...

    以上聲明爲letter元素定義規則。letter包含一個或多個contact元素、一個salutation、一個或多個paragraph元素,而 且必須按這個順序排列。加號(+)是“出現次數指示符”,表明元素必須出現一次或者多次;星號(*)表示一個可選元素可以出現任意次數;問號(?)表示一 個可選元素最多隻能出現一次。如果省略出現次數指示符,就默認爲剛好出現一次。

  11. 一個使用上面DTD的XML文檔例子(節選)

    	
    <?xml version = "1.0"?>
    <!DOCTYPE letter SYSTEM "letter.dtd">

    <letter>
    <contact type = "from">
    <name>test</name>
    <address1>test</address1>
    ...
    </contact>
    </letter>

    第一行是XML文檔的標準聲明,第二行引用一個DTD文檔,由三部份組成,1是DTD的根元素名稱(letter),2是關鍵字SYSTEM,指定一個外部DTDY文檔;3是DTD文檔的名稱和位置。

  12. Schema 是W3C推薦規範,XML社區的許多開發者都認爲DTD靈活性欠佳,不能滿足當今的編程需要。例如,程序不能採取與XML文檔相同的方式來處理DTD(例 如搜索,或者轉換成XHTML等不同的表示形式),因爲DTD本身不是XML文檔。正因爲存在這些限制,促使Schema的問世。和DTD不同, Schema不使用EBNF語法。相反,它使用XML語法,而且本質就是XML文檔,可能採取程序化方法進行處理。類似DTD,Schema也需要驗證解 析器。

  13. Schema使用XSV(XML Schema Validator,XML模式驗證程序)來驗證XML文檔。可在線使用XSV,請訪問http://www.w3.org/2000/09/webdata/xsv,要想下載XSV,請訪問http://www.ltg.ed.ac.uk/~ht/xsv-status.html

  14. 一個使用Schema的XML文檔例子

    <?xml version = "1.0"?>
    <!-- Document that conforms to a W3C XML Schema. -->

    <test:books xmlns:test = "http://www.xml.com/books">
    <book>
    <title>python how to program</title>
    </book>
    <book>
    <title>perl how to program</title>
    </book>
    </test:books>

  15. Schema文檔通常採用.xsd擴展名。這個例子是上面XML文檔的模式文檔。

    <?xml version = "1.0"?>
    <!-- Simple W3C XML Schema document. -->

    <xsd:schema xmlns:xsd = "http://www.w3.org/2001/XMLSchema"
    xmlns:test = "http://www.xml.com/books"
    targetNamespace = "http://www.xml.com/books" >

    <xsd:element name = "books" type = "test:BooksType" />

    <xsd:complexType name = "BooksType">
    <xsd:sequence>
    <xsd:element name = "book" type = "test:BooksType" minOccurs = "1" maxOccurs = "unbounded" />
    </xsd:sequence>
    </xsd:complexType>

    <xsd:complexType name = "BookType">
    <xsd:sequence>
    <xsd:element name = "title" type = "xsd:string" />
    </xsd:sequence>
    </xsd:complexType>

    </xsd:schema>

    在Schema中,element元素定義一個元素。name和type屬性分別指定元素名稱和數據類型。complexType元素定義一個複雜類型的元素類型,xsd:string定義title元素的數據類型。

  16. XML 允許作者自行創建標記,以便精確描述數據,各個學術領域的人和組織創建了大量XML詞彙表來結構化數據。其中一些詞彙表包括MathXML(數學標記語 言)、可擴展矢量圖形(SVG)、無線標記語言(WML)、可擴展商業報表語言(XBRL)、可擴展用戶界面語言(XUL)、以及VoiceXML。 Schema和XSL(可擴展樣式表語言)也都是XML語彙表的例子。

  17. MathXML由W3C開發,用於描述數學符號及表達式。以前是需要專業軟件包(如LaTeX)才能顯示覆雜的數學表達式。可解析和呈現MathXML的一個應用程序是W3C的Amaya瀏覽器/編輯器。可到以下網址下載http://www.w3.org/amaya/user/bindist.html

  18. 可 擴展樣式表語言(Extensible Stylesheet Language,XSL)是一種XML語彙表,用於格式化XML。XSLT是XSL的一部份,負責XSL轉換(XSLT),它可根據XML文檔創建以格 式化好的文本爲基礎的文檔。這個創建過程稱爲“轉換”,其中牽涉到兩個樹結構,即源樹(要轉換的XML文檔)和結果樹(轉換結果,例如XHTML)。完成 轉換後,源樹不會發生變化。要執行轉換,需要使用XSLT處理器。流行的XSLT處理器包括微軟的MSXML、APACHE SOFTWARE FOUNDATION的Xalan 2和Python的4XSLT包。

15.2. 常見編程錯誤

  1. 不正確地嵌套XML標記會導致出錯。

  2. 屬性值必須用雙引號或單引號封閉,否則就是錯誤。

  3. 以任何大小寫組合形式創建名爲xml的命名空間前綴都是錯誤的。

15.3. 移植性提示

  1. 儘管DTD是可選的,但DTD可使不同的程序生成的XML文檔保持一致,所以建議使用。

15.4. 軟件工程知識

  1. 屬性無需用命名空間前綴加以限定,因爲它們肯定同元素聯繫在一起。

  2. 儘管SAX已經獲得了廣泛的工業支持,但XML-DEV郵件列表的成員在開發SAX時是獨立於W3C的。DOM是正式的W3C推薦規範。

  3. 許多組織和個人都在積極地爲大範圍的應用程序(比如金融業務、醫藥處方等)創建DTD和Schema。這些集合統稱爲Repositions,通常可從Web免費下載,如http://www.dtd.com

15.5. 性能提示

  1. 處理大型XML文檔時,基於SAX的解析通常比基於DOM的解析更有效。尤其重要的是,SAX解析器不會將整個XML文檔載入內存。

  2. 如果文檔只需解析一次,最有效的就是基於SAX的解析,如果程序要從文檔快速獲取信息時,DOM解析通常比SAX解析更有效。要求節省內存的機器通常使用基於SAX的解析器。

15.6. 示例

Example 15.1. sort.xml

<?xml version="1.0"?>
<!-- XMLdocument containing book information. -->
<?xml-stylesheet type="text/xsl" href="sorting.xsl"?>
<book isbn="999-99999-9-x">
<title>Mary&apos;s XML Primer</title>
<author>
<firstName>Mary</firstName>
<lastName>White</lastName>
</author>
<chapters>
<frontMatter>
<preface pages="2"/>
<contents pages="5"/>
<illustrations pages="4"/>
</frontMatter>
<chapter number="3" pages="44">
advanced XML</chapter>
<chapter number="2" pages="35">
Intermediate XML</chapter>
<appendix number="B" pages="26">
Parsers and Tools</appendix>
<appendix number="A" pages="7">
Entities</appendix>
<chapter number="1" pages="28">
XML Fundamentals</chapter>
</chapters>
<media type="CD"/>
</book>

Example 15.2. sorting.xsl

<?xml version = "1.0"?>

<!-- Transformation of book information into XHTML. -->

<xsl:stylesheet version = "1.0"
xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">

<!-- write XML declaration and DOCTYPE DTD information -->
<xsl:output method = "xml" omit-xml-declaration = "no"
doctype-system =
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
doctype-public = "-//W3C//DTD XHTML 1.0 Strict//EN"/>

<!-- match document root -->
<xsl:template match = "/">
<html xmlns = "http://www.w3.org/1999/xhtml">
<xsl:apply-templates/>
</html>
</xsl:template>

<!-- match book -->
<xsl:template match = "book">
<head>
<title>ISBN<xsl:value-of select = "@isbn" />-<xsl:value-of select = "title" /></title>
</head>

<body>
<h1 style = "color: blue">
<xsl:value-of select = "title"/></h1>

<h2 style = "color: blue">by<xsl:value-of select = "author/lastName" />,
<xsl:value-of select = "author/firstName" /></h2>

<table style = "border-style: groove; background-color: wheat">

<xsl:for-each select = "chapters/frontMatter/*">
<tr>
<td style = "text-align: right">
<xsl:value-of select = "name()" />
</td>

<td>
( <xsl:value-of select = "@pages" /> pages )
</td>
</tr>
</xsl:for-each>

<xsl:for-each select = "chapters/chapter">
<xsl:sort select = "@number" data-type = "number" order= "ascending" />
<tr>
<td style = "text-align: right">
Chapter <xsl:value-of select = "@number" />
</td>

<td>
( <xsl:value-of select = "@pages" /> pages )
</td>
</tr>
</xsl:for-each>

<xsl:for-each select = "chapters/appendix">
<xsl:sort select = "number" data-type = "text" order = "ascending" />

<tr>
<td style = "text-align: right">
Appendix <xsl:value-of select = "@number" />
</td>

<td>
( <xsl:value-of select = "@pages" /> pages )
</td>
</tr>
</xsl:for-each>
</table>

<p style = "color: blue"> Pages:
<xsl:variable name = "pagecount"
select = "sum(chapters//*/@pages)" />
<xsl:value-of select = "$pagecount" />
<br/>Media Type:
<xsl:value-of select = "media/@type" /> </p>
</body>
</xsl:template>
</xsl:stylesheet>

Chapter 16. Python的XML處理

16.1. 知識點

  1. 在python中,可以使用DOM(文檔對象模型)和SAX(Simple API for XML)處理xml文檔。

16.2. 良好編程習慣

  1. 儘管在python2.0或更高版本中不需要調用releaseNode方法,但請堅持這樣做,以確保從內存中清除DOM樹。

16.3. 示例

Example 16.1. 動態生成XML內容

perl,perl
ok,ok
test,test
check,check
python,python
jython,jython
上面是用於動態生成XML的原始文檔。
#chapter16.1
#making up a text file's data as XML

import sys

print "Content-type:text/xml/n"

#write XML declaration and processing instruction
print """<?xml version = "1.0"?>
<?xml:stylesheet type = "text/xsl"
href = "name.xsl"?>"""

#open data file
try:
file = open( "names.txt","r" )
except IOError:
sys.exit( "Error opening file" )

print "<contacts>" #write root element

#list of tuples:(special character,entity reference)
replaceList = [ ( "&", "&amp;" ),
( "<", "&lt;" ),
( ">", "&gt;" ),
( '"', "&quot;" ),
( "'", "&apos;" ) ]

#replace special characters with entity reference
for currentLine in file.readlines():

for oldValue, newValue in replaceList:
currentLine = currentLine.replace( oldValue, newValue )

#extract lastname and firstname
last, first = currentLine.split( "," )
first = first.strip() #remove carriage return

#write contact element
print """ <contact>
<LastName>%s</LastName>
<FirstName>%s</FirstName>
</contact>""" % ( last, first )

file.close()

print "</contacts>"
以上是將文本轉換成XML的程序。

<?xml version = "1.0"?>

<!-- Formats a contact list -->

<xsl:stylesheet version = "1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!-- match document root -->
<xsl:template match = "/">

<html xmlns = "http://www.w3.org/1999/xhtml">
<head>
<title>Contact List</title>
</head>

<body>
<table border = "1">

<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>

<!-- process each contact element -->
<xsl:for-each select = "contacts/contact">
<tr>
<td>
<xsl:value-of select = "FirstName" />
</td>
<td>
<xsl:value-of select = "LastName" />
</td>
</tr>
</xsl:for-each>

</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
以上是把動態生成的XML轉換成HTML的XSLT。

Example 16.2. 一個XML論壇的例子

<?xml version = "1.0"?>
<!-- feedback.xml -->
<!-- XML document representing a forum -->

<?xml:stylesheet type = "text/xsl" href = "../XML/formatting.xsl"?>

<forum file = "feedback.xml">
<name>Feedback</name>
<message timestamp = "Web Jun 27 12:30:00 2004">
<user>python</user>
<title>my first forum</title>
<text>this is a xml forum.</text>
</message>
</forum>

--- 用於表示一個論壇的XML文檔(內含一篇文章)---
===============================================================================

<?xml version='1.0' encoding='UTF-8'?>
<!-- forums.xml -->
<!-- xml document containing all forums -->
<forums>
<forum filename='feedback.xml'>
<name>Feedback</name>
</forum>
</forums>

--- 顯示所有論壇的XML文檔(內含一個feedback論壇) ---
===============================================================================

#!c:/python23/python.exe
# filename: default.py
# Default page for message forums

import os
import sys
from xml.dom.ext.reader import PyExpat

def printHeader( title, style ):
print """Content-type: text/html

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml-strict.dtd">
<html xmlns = "http://www.w3.org/1999/xhtml">

<head>
<title>%s</title>
<link rel = "stylesheet" href = "%s" type = "text/css" />
</head>

<body>""" % ( title, style )

# open XML document that contains the forum names and locations
try:
XMLFile = open( "../htdocs/XML/forums.xml" )
except IOError:
print "Location: /error.html/n"
sys.exit()

# parse XML document containing forum information
reader = PyExpat.Reader()
document = reader.fromStream( XMLFile )
XMLFile.close()

# write XHTML to browser
printHeader( "my Forum", "/XML/site.css" )
print """<h1>my Forum</h1>
<p style = "font-weight:bold">Available Forum</p>
<ul>"""

# determine client-browser type
if os.environ[ "HTTP_USER_AGENT"].find( "MSIE" ) != -1:
prefix = "../XML/"
else:
prefix = "forum.py?file="

# add links for each forum
for forum in document.getElementsByTagName( "forum" ):

#create link to forum
link = prefix + forum.attributes.item( 0 ).value

#get element nodes containing tag name "name"
name = forum.getElementsByTagName( "name" )[ 0 ]

#get Text node's value
nameText = name.childNodes[ 0 ].nodeValue
print '<li><a href = "%s">%s</a></li>' % ( link, nameText )

print """</ul>
<p style= "font-weight:bold">Forum Management</p>
<ul>
<li><a href = "addForum.py">Add a Forum</a></li>
<li>Delete a Forum</li>
<li>Modify a Forum</li>
</ul>
</body>
</html>"""

reader.releaseNode( document )

--- 論壇的默認頁面 ---
===============================================================================

#!c:/python23/python.exe
# filename: addForum.py
# Adds a forum to the list

import re
import sys
import cgi

#4DOM packages
from xml.dom.ext.reader import PyExpat
from xml.dom.ext import PrettyPrint

def printHeader( title, style ):
print """Content-type: text/html

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml-strict.dtd">
<html xmlns = "http://www.w3.org/1999/xhtml">

<head>
<title>%s</title>
<link rel = "stylesheet" href = "%s" type = "text/css" />
</head>

<body>""" % ( title, style )

form = cgi.FieldStorage()

# if user enters data in form fields
if form.has_key( "name" ) and form.has_key( "filename" ):
newFile = form[ "filename" ].value

#determine whether file has xml extension
if not re.match( "/w+/.xml___FCKpd___10quot;, newFile ):
print "Location: /error.html/n"
sys.exit()
else:
# create forum files from xml files
try:
newForumFile = open( "../htdocs/XML/" + newFile, "w" )
forumsFile = open( "../htdocs/XML/forums.xml", "r+" )
templateFile = open ( "../htdocs/XML/template.xml" )
except IOError:
print "Location: /error.html/n"
sys.exit()

# parse forums document
reader = PyExpat.Reader()
document = reader.fromStream( forumsFile )

#add new forum element
forum = document.createElement( "forum" )
forum.setAttribute( "filename", newFile )

name = document.createElement( "name" )
nameText = document.createTextNode( form[ "name" ].value )
name.appendChild( nameText )
forum.appendChild( name )

# obtain root element of forum
documentNode = document.documentElement
firstForum = documentNode.getElementsByTagName( "forum" )[ 0 ]
documentNode.insertBefore( forum, firstForum )

# write update XML to disk
forumsFile.seek(0, 0)
forumsFile.truncate()
PrettyPrint( document, forumsFile )
forumsFile.close()

# create document for new forum from template file
document = reader.fromStream( templateFile )
forum = document.documentElement
forum.setAttribute( "file", newFile )

# create name element
name = document.createElement( "name" )
nameText = document.createTextNode( form[ "name" ].value )
name.appendChild( nameText )
forum.appendChild( name )

# write generated XML to new forum file
PrettyPrint( document, newForumFile )
newForumFile.close()
templateFile.close()
reader.releaseNode( document )

print "Location: default.py/n"

else:
printHeader( "Add a forum", "/XML/site.css" )
print """<form action = "addForum.py" metod = "post">
Forum Name<br />
<input type = "text" name = "name" size = "40" /><br />
Forum File Name<br />
<input type = "text" name = "filename" size = "40" /><br />
<input type = "submit" name = "submit" value = "Submit" />
<input type = "reset" value = "Reset" />
</form>

<a href = "/cgi-bin/default.py"> Return to Main Page.</a>
</body>
</html>"""

--- 向forums.xml添加新論壇的腳本 ---
===============================================================================

<?xml version = "1.0"?>

<!-- empty forum file template.xml -->

<?xml:stylesheet type = "text/xsl" href = "../XML/formatting.xsl"?>

<forum>
</forum>

---用於生成新論壇的XML模板 ---
===============================================================================

#!c:/python23/python.exe
# Adds a message to a forum

import re
import os
import sys
import cgi
import time

#4DOM packages
from xml.dom.ext.reader import PyExpat
from xml.dom.ext import PrettyPrint

def printHeader( title, style ):
print """Content-type: text/html

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml-strict.dtd">
<html xmlns = "http://www.w3.org/1999/xhtml">

<head>
<title>%s</title>
<link rel = "stylesheet" href = "%s" type = "text/css" />
</head>

<body>""" % ( title, style )

# identify client browser
if os.environ[ "HTTP_USER_AGENT" ].find( "MSIE" ) != -1:
prefix = "../XML/" #Internet Explorer
else:
prefix = "forum.py?file="

form = cgi.FieldStorage()

#user has submitted message to post
if form.has_key( "submit" ):
filename = form[ "file" ].value

# add message to forum
if not re.match( "/w+/.xml___FCKpd___10quot;, filename ):
print "Location: /error.html/n"
sys.exit()

try:
forumFile = open( "../htdocs/XML/" + filename, "r+" )
except IOError:
print "Location: /error.html/n"
sys.exit()

# parse forum document
reader = PyExpat.Reader()
document = reader.fromStream( forumFile )
documentNode = document.documentElement

# create message element
message = document.createElement( "message" )
message.setAttribute( "timestamp", time.ctime( time.time() ) )

# add elements to message
messageElements = [ "user", "title", "text" ]

for item in messageElements:

if not form.has_key( item ):
text = "( Field left blank )"
else:
text = form[ item ].value

# create nodes
element = document.createElement( item )
elementText = document.createTextNode( text )
element.appendChild( elementText )
message.appendChild( element )

#append new message to forum and update document on disk
documentNode.appendChild( message )
forumFile.seek(0,0)
forumFile.truncate()
PrettyPrint( document, forumFile )
forumFile.close()
reader.releaseNode( document )

print "Location: %s/n" % ( prefix + form[ "file" ].value )

# create form to obtain new message
elif form.has_key( "file" ):
printHeader( "Add a posting", "/XML/site.css" )
print """/n<form action = "addPost.py" method = "post">
User<br />
<input type = "text" name = "user" size = "40" /><br />
Message Title<br />
<input type = "text" name = "title" size = "40" /><br />
Message Text<br />
<textarea name = "text" cols = "40" rows = "5"></textarea><br />
<input type = "hidden" name = "file" value = "%s" />
<input type = "submit" name = "submit" value = "Submit" />
<input type = "reset" value = "Reset" />
</form>

<a href = "%s">Return to Forum</a>
</body>

</html>""" % ( form[ "file" ].value, prefix + form[ "file" ].value )
else:
print "Location: /error.html/n"

--- 爲論壇添加文章的腳本 ---
===============================================================================

<?xml version = "1.0"?>

<!-- Style sheet for forum files formatting.xsl. -->

<xsl:stylesheet version = "1.0"
xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">

<!-- match document root -->
<xsl:template match = "/">
<html xmlns = "http://www.w3.org/1999/xhtml">

<!-- apply templates for all elements -->
<xsl:apply-templates select = "*" />
</html>
</xsl:template>

<!-- match forum element. -->
<xsl:template match = "forum">
<head>
<title><xsl:value-of select = "name" /></title>
<link rel = "stylesheet" type = "text/css" href = "../XML/site.css" />
</head>

<body>
<table width = "100%" cellspacing = "0" cellpadding = "2">
<tr>
<td class = "forumTitle">
<xsl:value-of select = "name" />
</td>
</tr>
</table>

<!-- apply templates for message elements -->
<br />
<xsl:apply-templates select = "message" />
<br />

<div style = "text-align: centen">
<a>
<!--add href attribute to "a" element -->
<xsl:attribute name = "href">../cgi-bin/addPost.py?file=
<xsl:value-of select = "@file" />
</xsl:attribute>
Post a Message
</a>
<br /><br />
<a href = "../cgi-bin/default.py">Return to Main Page</a>
</div>

</body>
</xsl:template>

<!-- match message elements -->
<xsl:template match = "message">
<table width = "100%" cellspacing = "0" cellpadding = "2">
<tr>
<td class = "msgTitle">
<xsl:value-of select = "title" />
</td>
</tr>

<tr>
<td class = "msgInfo">
by
<xsl:value-of select = "user" />
at
<span class = "date">
<xsl:value-of select = "@timestamp" />
</span>
</td>
</tr>

<tr>
<td class = "msgText">
<xsl:value-of select = "text" />
</td>
</tr>

</table>
</xsl:template>

</xsl:stylesheet>
--- 將XML轉換成XHTML的XSLT樣式表 ---
===============================================================================

#!c:/python23/python.exe
#display forum postings for non-Internet Explorer browser.

import re
import cgi
import sys
from xml.xslt import Processor

form = cgi.FieldStorage()

# form to display has been specified
if form.has_key( "file" ):

# determine whether file is xml
if not re.match( "/w+/.xml___FCKpd___10quot;, form[ "file" ].value ):
print "Location: /error.html/n"
sys.exit()

try:
style = open( "../htdocs/XML/formatting.xsl" )
XMLFile = open( "../htdocs/XML/" + form[ "file" ].value )
except IOError:
print "Location: /error.html/n"
sys.exit()

# create XSLT processor instance
processor = Processor.Processor()

# specify style sheet
processor.appendStylesheetStream( style )

# apply style sheet to XML document
results = processor.runStream( XMLFile )
style.close()
XMLFile.close()
print "Content-type: text/html/n"
print results
else:
print "Location: /error.html/n"

--- 爲不支持XSLT的瀏覽器將XML轉換成HTML ---

Chapter 17. 數據庫應用程序編程接口(DB-API)

17.1. 知識點

  1. python程序員使用遵循Python DB-API(數據庫應用程序編程接口)規範的模塊與數據庫通信。

  2. DB-API描述了一個用於訪問(連接)數據庫的Connection對象。程序可用該對象創建Cursor對象,該對象負責處理和接收數據。

  3. 利 用Cursor對象,可用3種方法從查詢結果集中獲取行,即fetchone、fetchmany和fetchall。fetchone方法返回一個元 組,其中包含存儲在Cursor中的一個結果集的下一行;fetchmany方法需要指定一個參數,即返回行的行數,並以元組的元組形式,從結果集中返回 後續一系列行;fetchall方法則採用“元組的元組”的形式,返回結果集中的所有行。

  4. DB-API的優點是,程序能方便地連接各種數庫,python代碼修改量很少,但不同數據庫間轉換時SQL代碼需作修改。

17.2. 良好的編程習慣

  1. 習慣上,在不區分大小寫的系統上,SQL關鍵字要全部採用大寫字母。以突出顯示SQL的關鍵字。

  2. 程序不再需要Cursor和Connection對象後,用相應的close方法顯式關閉它們。

17.3. 常見編程錯誤

  1. SQL語句將單引號(')作爲字符串的定界符。如果字符串本身包含有一個單引號(如 don't),就必須用兩個單引號來表示一個單引號(如 don''t)。如果沒有這樣轉義,就會造成SQL語句語法錯誤。

17.4. 移植性提示

  1. 並非所有數據庫系統都支持like運算符,有的數據庫在like表達式中使用的不是%,而是*。

Chapter 18. 進程管理

18.1. 知識點

  1. 有 兩種方式來實現併發性,一種方式是讓每個“任務"或“進程”在單獨的內在空間中工作,每個都有自已的工作內存區域。不過,雖然進程可在單獨的內存空間中執 行,但除非這些進程在單獨的處理器上執行,否則,實際並不是“同時”運行的。是由操作系統把處理器的時間片分配給一個進程,用完時間片後就需退出處理器等 待另一個時間片的到來。另一種方式是在在程序中指定多個“執行線程”,讓它們在相同的內存空間中工作。這稱爲“多線程處理”。線程比進程更有效,因爲操作 系統不必爲每個線程創建單獨的內存空間。

  2. 新建進程用os.fork函數。但它只在POSIX系統上可用,在windows版的python中,os模塊沒有定義os.fork函數。相反,windows程序員用多線程編程技術來完成併發任務。

  3. os.fork 函數創建進程的過程是這樣的。程序每次執行時,操作系統都會創建一個新進程來運行程序指令。進程還可調用os.fork,要求操作系統新建一個進程。父進 程是調用os.fork函數的進程。父進程所創建的進程叫子進程。每個進程都有一個不重複的進程ID號。或稱pid,它對進程進行標識。子進程與父進程完 全相同,子進程從父進程繼承了多個值的拷貝,如全局變量和環境變量。兩個進程的唯一區別是fork的返回值。子進程接收返回值0,而父進程接收子進程的 pid作爲返回值。

  4. 用os.fork創建的子進程和父進程作爲異步的併發進程而單獨執行。異步是指它們各行其是,相互間不進行同步;併發是指它們可同時執行。所以我們無法知道子進程和父進程的相對速度。

  5. os.wait 函數用於等待子進程結束(只適用於UNIX兼容系統)。該函數返回包含兩個元素的元組,包括已完成的子進程號pid,以及子進程的退出狀態,返回狀態爲 0,表明子進程成功完成。返回狀態爲正整數表明子進程終止時出錯。如沒有子進程,會引發OSError錯誤。os.wait要求父進程等待它的任何一個子 進程結束執行,然後喚醒父進程。

  6. 要指示父進程等候一個指定的子進程終止,可在父進程中使用os.waitpid函數(只 適用於unix兼容系統)。它可等候一個指定進程結束,然後返回一個雙元素元組,其中包括子進程的pid和子進程的退出狀態。函數調用將pid作爲第一個 參數傳遞,並將一個選項作爲第二個選項,如果第一個參數大於0,則waitpid會等待該pid結束,如果第一個參數是-1,則會等候所有子進程,也就和 os.wait一樣。

  7. 用os.system 和 os.exec函數族來執行系統命令和其它程序。os.system使用shell來執行系統命令,然後在命令結束之後把控制權返回給原始進程; os.exec函數族在執行完命令後不將控制權返回給調用進程。它會接管python進程,pid不變。這兩個函數支持unix和windows平臺。

  8. os.popen ()函數可執行命令,並獲得命令的stdout流。函數要取得兩個參數,一個是要執行的命令,另一個是調用函數所用的模式,如“r"只讀模式。 os.popen2()函數執行命令,並獲得命令的stdout流和stdin流。函數返回一個元組,其中包含有兩個文件對象,一個對象對應stdin 流,一個對象對應stdout流。

  9. 進程使用IPC機制在進程間傳遞信息,一種IPC機制是“管道”,它是一種類似於文件 的對象,提供單向通信渠道。父進程可打開一個管道,再分支一個子進程。父進程使用管道將信息寫入(發送到)子進程,而子進程使用管道從父進程讀取信息。在 python中使用os.pipe函數創建管道。

  10. os._exit()類似於sys.exit(),但它不執行任何的清 除工作(例如刷新緩衝區)。所以os._exit()尤其適用於退出子進程。如果程序使用sys.exit(),操作系統會回收父進程或其它子進程可能仍 然需要的資源。傳給os._exit()函數的參數必須是進程的退出狀態。退出狀態爲0,表示正常終止。

  11. 進程也可用信號 進行通信。所謂“信號”,是操作系統採取異步方式傳給程序的消息。如CTRL+C會傳遞一個“中斷信號”,通常該信號導致程序中止。然而程序完全可以指定 用不同的行動來響應任何一個信號。在信號處理中,程序要接收信號,並根據那個信號採取一項行動。錯誤(例如向已關閉管道寫入)、事件(例如計時器變成0) 以及用戶輸入(例如按ctrl+c)都會產生信號。

  12. 針對每個信號,每個python程序都有一個默認的信號處理程序。例 如,假定python解釋器收到一個信號,該信號指出程序試圖向已關閉的管道寫入,或者用戶敲入一個鍵盤中斷,python就會引發一個異常。發生異常 後,程序既可使用默認處理程序,也可使用自定義處理程序。

  13. signal.signal函數爲中斷信號註冊一個信號處理程序。函數要獲得兩個參數:一個信號和一個對應於信號處理程序的函數。

  14. 在unix/linux 系統中,子進程終止後,會保留在進程表中,讓父進程知道子進程是否正常終止。如果創建大量子進程,但在終止後沒有從進程表中移除它們,進程表便會積累越來 越多的死進程,這些進程稱爲“zombies”(殭屍進程),消除殭屍進程的操作稱爲“reaping”,這是通過os.wait和os.waitpid 函數實現的。

18.2. 良好的編程習慣

  1. 進程應關閉不需要的管道端,因爲操作系統限制了可同時打開的文件說明符數量。

18.3. 移植性提示

  1. 並不是所有操作系統都能從一個正在運行的程序創建單獨的進程,所以,進程管理是移植性最差的一項python特性。

  2. 每個系統都定義了特有信號集。signal是依賴於具體平臺的模塊,其中只包含系統定義的信號。

Chapter 19. 多線程處理

19.1. 知識點

  1. 線程是“輕量級”進程,因爲相較於進程的創建和管理,操作系統通常會用較少的資源來創建和管理線程。操作系統要爲新建的進程分配單獨的內在空間和數據;相反,程序中的線程在相同的內存空間中執行,並共享許多相同的資源。多線程程序在結內存的使用效率要優於多進程程序。

  2. python 提供了完整的多線程處理類,如果操作系統支持多線程,就可用python的threading模塊創建多線程應用程序。程序員可以在一個應用程序中包含多 個執行線程,而且每個線程都表明程序中的一部份要與其他線程併發執行。許多應用程序都可獲益於多線程編程。Web瀏覽器下載大文件時(比如音樂或視頻), 用戶希望立即可欣賞音樂或觀看視頻,這樣就可以讓一個線程下載,另一個線程播放已經下載的一部分。從而實現多個操作併發執行。

19.2. 性能提示

  1. 單線程程序問題在於要在結束費時較長的操作後,才能開始其它操作。而在多線程程序中,線程可共享一個或多個處理器,使多個任務並行執行。

  2. 解 釋器開始執行程序時,“主”線程開始執行。每個線程都可創建和啓動其它線程。如果程序包含多個正在運行的線程,它們將依據指定的間隔時間(稱爲一個 quantum),依次進入和離開解釋器。Python的“全局解釋器鎖”(Global Interpreter Lock,GIL)保證解釋器在任何時刻只運行一個線程。GIL每次可用時,都會有單個線程包含它,然後,線程進入解釋器,關在該線程的quantum時 間段中執行它。一旦quantum到期,線程就離開解釋器,同時釋放GIL。

  3. 在任何時刻,線程都處於某種線程狀態。新線 程將從“born”狀態開始它的生命週期。線程保持這個狀態,直到程序調用線程的start方法,這會使線程進入“ready”(就緒)狀態,有時也稱爲 “runnable”狀態。另外,控制權會立即返回至調用線程(調用者)。之後,調用者可與已啓動的線程以及程序中的其他任何線程併發執行。當 “ready”線程首次獲得GIL(Global Interpreter Lock,全局解釋器鎖),會執行它的run方法,成爲一人“running”(正在運行)線程。run方法會一直執行,直到線程引發一個未處理的異常, 或者線程離開解釋器。running線程離開解釋器時,線程會記住它的當前執行位置。以後線程重新進入解釋器,線程會從該位置繼續執行。線程惟一能獲得 GIL的狀態就是“running”狀態。

  4. run方法返回或終止(如遇到一個未進行捕捉的異常),就會進入“dead”狀態。解釋器最終會對dead線程進行處理。如果running線程調用另一個線程的join方法,running線程會失去GIL,並等待加入的方法死亡之後纔會繼續。

  5. 大 多數程序都使用外部資源(比如網絡連接和磁盤文件)來執行任務。如果線程請求的資源不可用,線程就會進入“blocked”(暫停或阻塞)狀態,直到資源 再次可用,線程發了I/O請求後(比如從磁盤上讀入文件,或將文件發送到打印機),會失去GIL,並離開解釋器。其它線程就中使用解釋器,從而可高效利用 處理器,有助縮短程序的總體執行時間。I/O操作完成後,在“blocked”狀態等候它的線程進入“ready”狀態,之後,線程就會試圖重新獲得 GIL。

  6. “running”線程調用time.sleep函數後,會釋放GIL並進入“sleeping”(休眠)狀態。指定的休眠時間到期,“sleeping”線程會返回“ready”狀態。即使解釋器可用,“sleeping”線程也不能使用解釋器。

  7. 程序中的線程通常共享數據。如果多個線程修改相同的數據,數據會出錯,在這樣的程序中,需要同步對共享數據的訪問。這意味着訪問共享數據的每個線程首先必須獲得與數據對應的一個同步對象鎖。一旦線程處理完數據,就應釋放同步對象,使其它線程能訪問數據。

  8. 有 時因爲一個程序的邏輯需求,正在運行的線程即使爲共享數據獲得了同步對象,也不能對其執行操作。這種情況下,線程可調用同步對象的wait方法以主動釋放 對象。這會導致線程釋放GIL並針對那個同步對象進入“waiting”狀態。另一個線程調用同步對象的notify方法時,那個同步對象的一個 “waiting”線程會變成“ready”狀態。在重新獲得GIL後,線程就會恢復執行。“running”線程調用同步對象的notifyAll方 法,處於“waiting”狀態的每個線程都會變成“ready”狀態。然後,解釋器選擇一個“ready”線程來執行。

  9. threading.currentThread函數會返回對當前正在運行的線程的一個引用。

  10. threading.enumerate函數返回一個列表,其中包含Thread類的當前所有活動對象(即run方法開始但未終止的任何線程)。

  11. threading.activeCount函數返回上面列表的長度。

  12. isAlive可測試線程是否死亡,如果返回1代表線程正在運行;setName方法設置線程名稱;getName方法返回線程名稱;爲一個線程使用print方法會顯示線程名稱及其當前狀態。

  13. 通常將訪問共享數據的代碼區稱爲“臨界區”

  14. 訪 問共享數據的每個線程都禁止其他所有線程同時訪問相同的數據。這稱爲“獨佔”或“線程同步”。threading模塊提供了許多線程同步機制。最簡單的同 步機制是“鎖”。鎖對象用threading.RLock類創建,它定義了兩個方法,即acquire(獲得)和release(釋放)。線程調用 acquire方法,鎖會進入“locked”(鎖定)狀態,每次只有一個線程可獲得鎖。如另一個線程試圖對同一個鎖對象調用acquire方法,操作系 統會將那個線程轉變爲“blocked”狀態,直到鎖變得可用。擁有鎖的線程調用release方法,鎖會進入“unlocked”(解鎖)狀態。 “blocked”的線程會收到一個通知,並可獲得鎖。如果多個鎖處於“blocked”狀態,所有線程都會先解除該狀態。然後,操作系統選擇一個線程來 獲得鎖,再將其餘線程變回“blocked”狀態。

  15. 在多線程程序中,線程必須先獲得一個鎖,再進入臨界區;退出臨界區時,則要釋放鎖。

  16. 在 複雜的線程中,有時只在發生某些事件時才訪問一個臨界區(比如在某個數據值改變時)。這是通過“條件變量”來完成的。線程用條件變量來監視一個對象的狀 態,或者發出事件通知。對象狀態改變或事件發生時,處於blocked狀態的線程會收到通知。收到通知後線程才訪問臨界區。

  17. 條 件變量用threading.Condition類創建。條件變量包含基本鎖,所以它們提供了acquire和release方法。條件變量的其它方法還 有wait和nofify。線程成功獲得一個基本鎖後,調用wait方法會導致調用線程釋放這個鎖,並進入“blocked”狀態,直到另一個線程調用同 一個條件變量的notify方法,從而將其喚醒。notify方法可喚醒一個正在等待條件變量的線程;notifyAll則喚醒所有正在等待的方法。

  18. 下 面通過一個“生產者/消費者”的關係,說明線程同步的應用情況。在這個關係中,應用程序的“生產者”部分生成數據,“消費者”部分使用數據,在多線程的生 產者/消費者關係中,“生產者線程”調用一個“生產方法”來生成數據,並將數據放到名爲“緩衝區”的共享內存區域。“消費者線程”則調用一個“消費方法” 來讀取數據。如果正在等待放入下一批數據的生產者線程發現消費者線程尚未從緩衝區中讀取上一批數據,生產者線程就會調用條件變量的wait方法;否則,消 費者線程將無法看到上一批數據。消費者線程讀取數據時,應調用條件變量的notify方法,使正在等待的生產者線程繼續。如果消費者線程發現緩衝區爲空, 或上一批數據已讀取,就應調用條件變量的wait方法;否則,消費者線程會從緩衝區讀入“垃圾”數據,或者重複處理以前的數據項--任何一種可能都會導致 應用程序出現邏輯錯誤。生產者將下一批數據放入緩衝區時,生產者線程應調用條件變量的notify方法,讓消費者線程繼續。

  19. 爲儘可能縮短共享資源並以相同相對速度工作的各線程的等待時間,可用一個“隊列(Queue)”來提供額外的緩衝區,便於生產者在其中放值,也便於消費者從中獲得那些值。

  20. python提供一個Queue模塊,該模塊定義一人Queue類,即隊列的一個同步實現。

  21. “信號機”(Semaphore)是一個變量,控制着對公共資源或臨界區的訪問。信號機維護着一個計數器,指定可同時使用資源或進入臨界區的線程數。每次有一個線程獲得信號機時,計數器都會自減。若計數器爲0,其它線程便只能暫停訪問信號機,直到另一個線程釋放信號機。

  22. threading模塊定義了可用於線程通信的Event(事件)類。Event有一個內部標記,可爲true或false。一個或多個線程能調用Event對象的wait方法以暫停並等待事件發生。事件發生後,暫停的線程會按它們抵達的順序被喚醒,並恢復執行。

19.3. 常見編程錯誤

  1. 必須把所有臨界區都封閉在acquire和release調用之間。

  2. 如果程序使用了鎖,就要仔細檢查,保證程序不會死鎖。如果程序或線程永遠處於“blocked”狀態,就會發生死鎖。

  3. 等待一個條件變量的線程必須用notify顯式喚醒,否則它會永遠地等待下去,導致死鎖。

19.4. 測試和調試提示

  1. 保證每個wait調用都有一個對應的notify調用,以最終結束等待,也可調用notifyAll以策萬全。

19.5. 性能提示

  1. 通過同步來確保多線程程序的正確性,可能會減慢程序的運行速度,這是由於鎖造成了額外的開銷,而且需在線程的不同狀態間頻繁切換。

Chapter 20. 聯網

20.1. 知識點

  1. python提供流套接字(tcp)和數據報套接字(udp)。

  2. urlparse模塊提供了用於解析url的函數,以及用於url處理的函數。

  3. 要在python中建立具有TCP和流套接字的簡單服務器,需要使用socket模塊。利用該模塊包含的函數和類定義,可生成通過網絡通信的程序。建立這個連接需要6個步驟:

    • 第一步是創建socket對象。調用socket構造函數。如:

      socket = socket.socket( family, type )

      family參數代表地址家族,可爲AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用於同一臺機器上的進程間通信。

      type參數代表套接字類型,可爲SOCK_STREAM(流套接字)和SOCK_DGRAM(數據報套接字)。

    • 第二步是將socket綁定到指定地址。這是通過socket對象的bind方法來實現的:

      socket.bind( address )

      由AF_INET所創建的套接字,address地址必須是一個雙元素元組,格式是(host,port)。host代表主機,port代表端口號。如果端口號正在使用、主機名不正確或端口已被保留,bind方法將引發socket.error異常。

    • 第三步是使用socket套接字的listen方法接收連接請求。

      socket.listen( backlog )

      backlog指定最多允許多少個客戶連接到服務器。它的值至少爲1。收到連接請求後,這些請求需要排隊,如果隊列滿,就拒絕請求。

    • 第四步是服務器套接字通過socket的accept方法等待客戶請求一個連接。

      connection, address = socket.accept()

      調 用accept方法時,socket會時入“waiting”狀態。客戶請求連接時,方法建立連接並返回服務器。accept方法返回一個含有兩個元素的 元組(connection,address)。第一個元素connection是新的socket對象,服務器必須通過它與客戶通信;第二個元素 address是客戶的Internet地址。

    • 第五步是處理階段,服務器和客戶端通過send和recv方法通信(傳輸 數據)。服務器調用send,並採用字符串形式向客戶發送信息。send方法返回已發送的字符個數。服務器使用recv方法從客戶接收信息。調用recv 時,服務器必須指定一個整數,它對應於可通過本次方法調用來接收的最大數據量。recv方法在接收數據時會進入“blocked”狀態,最後返回一個字符 串,用它表示收到的數據。如果發送的數據量超過了recv所允許的,數據會被截短。多餘的數據將緩衝於接收端。以後調用recv時,多餘的數據會從緩衝區 刪除(以及自上次調用recv以來,客戶可能發送的其它任何數據)。

    • 第六步,傳輸結束,服務器調用socket的close方法關閉連接。

  4. 在python中建立一個簡單客戶需要4個步驟。

    • 第一步是創建一個socket以連接服務器:

      socket = socket.socket( family, type )

    • 第二步是使用socket的connect方法連接服務器。對於AF_INET家族,連接格式如下:

      socket.connect( (host,port) )

      host代表服務器主機名或IP,port代表服務器進程所綁定的端口號。如連接成功,客戶就可通過套接字與服務器通信,如果連接失敗,會引發socket.error異常。

    • 第三步是處理階段,客戶和服務器將通過send方法和recv方法通信。

    • 第四步,傳輸結束,客戶通過調用socket的close方法關閉連接。

20.2. 常見編程錯誤

  1. 套接字send方法只接受一個字符串參數。傳遞不同類型的值(比如整數)會出錯。

20.3. 軟件工程知識

  1. 端口號範圍在0--65535之間,很多操作系統爲系統服務保留了1024以下的端口號。應用程序在得到特別授權後才能使用這些保留端口號。服務器端應用程序一般不要把1024以下的端口號指定爲連接端口。

  2. 利用python的多線程能力,程序員可創建多線程服務器,以便同時管理多個併發的客戶連接。

  3. 多線程服務器可用每個accept調用返回的套接字來創建一個線程,由它來管理通過該套接字進行的網絡IO。另外,可讓多線程服務器維護一個線程池,以便管理通過新建套接字進行的網絡IO。

20.4. 性能提示

  1. 在內存充足的高性能系統中,多線程服務器可創建一個線程池。可快速分配這些線程,以管理通過每個新建套接字進行的網絡IO。以後收到連接請求時,服務器不會產生創建線程的開銷。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章