Python入門 |
時間:2004/03/28 來源:不詳 |
第一章 介紹 腳本語言是類似DOS批處理、UNIX shell程序的語言。腳本語言不需要每次編譯再執(zhí)行,并且在執(zhí)行中可以很容易地訪問正在運行的程序,甚至可以動態(tài)地修改正在運行的程序,適用于快速地開發(fā)以及完成一些簡單的任務(wù)。在使用腳本語言時常常需要增的新的功能,但有時因為腳本語言本來就已經(jīng)很慢、很大、很復(fù)雜了而不能實現(xiàn);或者,所需的功能涉及只能用C語言提供的系統(tǒng)調(diào)用或其他函數(shù)——通常所要解決的問題沒有重要到必須用C語言重寫的程度;或者,解決問題需要諸如可變長度字符串等數(shù)據(jù)類型(如文件名的有序列表),這樣的數(shù)據(jù)類型在腳本語言中十分容易而C語言則需要很多工作才能實現(xiàn);或者,編程者不熟悉C語言:這些情況下還是可以使用腳本語言的。在這樣的情況下,Python可能正好適合你的需要。Python使用簡單,但它是一個真正的程序語言,而且比shell提供了更多結(jié)構(gòu)和對大型程序的支持。另一方面,它比C提供更多的錯誤檢查,它是一個非常高級的語言,內(nèi)置了各種高級數(shù)據(jù)結(jié)構(gòu),如靈活的數(shù)組和字典,這些數(shù)據(jù)結(jié)構(gòu)要用C高效實現(xiàn)的話可能要花費你幾天的時間。由于Python具有更一般的數(shù)據(jù)結(jié)構(gòu),它比Awk甚至Perl適用的范圍都廣,而許多東西在Python內(nèi)至少和在這些語言內(nèi)一樣容易。 Python允許你把程序分解為模塊,模塊可以在其他Python程序中重用。它帶有一大批標(biāo)準(zhǔn)模塊可以作為你自己的程序的基礎(chǔ)——或作為學(xué)習(xí)Python編程的例子。系統(tǒng)還提供了關(guān)于文件輸入輸出、系統(tǒng)調(diào)用、插座(sockets)的東西,甚至提供了窗口系統(tǒng)(STDWIN)的通用接口。 Python是一個解釋性語言,因為不需要編譯和連接所以能節(jié)省大量的程序開發(fā)時間。解釋程序可以交互使用,這樣可以可以很容易地試驗語言的各種特色,寫只用一次的程序,或在從底向上程序開發(fā)中測試函數(shù)。它也是一個方便的計算器。 Python允許你寫出非常嚴(yán)謹(jǐn)而且可讀的程序。用Python寫的程序通常都比相應(yīng)的C程序要短,因為如下幾個理由: 高級的數(shù)據(jù)結(jié)構(gòu)允許你用一個語句表達復(fù)雜的操作; 復(fù)合語句是靠縮進而不是用表示開始和結(jié)束的括號; 不需要變量聲明或參量聲明。 Python是可擴充的:如果你會用C語言編程就很容易為解釋程序增加新的內(nèi)置函數(shù)或模塊,這樣可以以最快速度執(zhí)行關(guān)鍵操作,或把Python程序和只能以二進制碼提供的庫(如不同廠商提供的圖形庫)連接起來。當(dāng)你變得確實很在行時你可以把Python解釋器與用C寫的應(yīng)用相連接,把它作為該應(yīng)用的擴展或命令語言。 Python的命名是由BBC的“Monty Python's Flying Circus”節(jié)目而得,與蟒蛇沒有什么關(guān)系。 第二章 解釋程序的使用 在命令行鍵入 python 或在Windows環(huán)境下雙擊相應(yīng)的圖標(biāo)可以進入Python的解釋程序。如果要運行儲存在文件中的Python程序,可以用 python 文件名 的形式。 進入解釋程序的環(huán)境后,解釋程序稱為處于交互狀態(tài)。在這種狀態(tài)下系統(tǒng)用 主提示提示輸入下一個命令,這一般是三個大于號(>>>),如果需要續(xù)行系統(tǒng)用 次提示提示輸入,缺省為三個小數(shù)點(...)。在主提示下鍵入文件尾符號(在UNIX中為Control-D,在DOS或Windows中為Control-Z)可以正常退出解釋程序。 Python解釋程序的有些版本支持命令行編輯和命令歷史,使用用Emacs或vi的鍵組合。 第三章 基本使用 下面我們用例子來介紹Python的基本用法。在例子中,用戶輸入和系統(tǒng)輸出靠有沒有提示(>>>和...)來分別。如果要試這些例子的話,需要鍵入提示后的所有命令,例子中沒有提示的行是系統(tǒng)的輸出。注意只有次提示的行意味著需要鍵入一個空行,這用于結(jié)束多行命令。 3.1 用Python作計算器使用 啟動解釋程序,等待主提示>>>出現(xiàn)。解釋程序可以作為計算器使用。鍵入一個表達式,解釋程序就可以輸出結(jié)果。表達式的寫法很直觀:+,-,*,/, %, **等算符的作用和其它大多數(shù)語言(如Pascal或C)沒什么差別,括號可以用來組合。例如: >>> 2+2 4 >>> # 這是一個注釋 ... 2+2 4 >>> 2+2 # 和代碼在同一行的注釋 4 >>> (50-5*6)/4 5 >>> # 整數(shù)除法得下面的整數(shù) ... 7/3 2 >>> 7/-3 -3 >>> 和C中一樣,等于號用來給變量賦值,賦值的結(jié)果不顯示: >>> width = 20 >>> height = 5*9 >>> width * height 900 >>> 可以同時給幾個變量賦同一個值: >>> x = y = z = 0 # 把 x, y 和 z賦零 >>> x 0 >>> y 0 >>> z 0 >>> Python完全支持浮點數(shù),混合類型的運算會把整數(shù)先轉(zhuǎn)換成浮點數(shù): >>> 4 * 2.5 / 3.3 3.0303030303 >>> 7.0 / 2 3.5 >>> Python也提供了復(fù)數(shù),方法是用j和J作為虛數(shù)單位,如1+1j,3.14e-10j,等等。 3.2. 字符串 Python除處理數(shù)字外還可以處理字符串,字符串用單撇號或雙撇號包裹: >>> 'spam eggs' 'spam eggs' >>> 'doesn\'t' "doesn't" >>> "doesn't" "doesn't" >>> '"Yes," he said.' '"Yes," he said.' >>> "\"Yes,\" he said." '"Yes," he said.' >>> '"Isn\'t," she said.' '"Isn\'t," she said.' >>> 字符串輸出格式與輸入的樣子相同,都是用撇號包裹,撇號和其它特殊字符用用反斜杠轉(zhuǎn)義。如果字符串中有單撇號而沒有雙撇號則用雙撇號包裹,否則應(yīng)該用單撇號包裹。后面要介紹的print語句可以不帶撇號或轉(zhuǎn)義輸出字符串。 字符串可以用+號連接起來,用*號重復(fù): >>> word = 'Help' + 'A' >>> word 'HelpA' >>> '<' + word*5 + '>' '<HelpAHelpAHelpAHelpAHelpA>' >>> 字符串可以象在C中那樣用下標(biāo)索引,字符串的第一個字符下標(biāo)為0。 Python沒有單獨的字符數(shù)據(jù)類型,一個字符就是長度為一的字符串。象在Icon語言中那樣,可以用片段(slice)記號來指定子串,片段即用冒號隔開的兩個下標(biāo)。 >>> word[4] 'A' >>> word[0:2] 'He' >>> word[2:4] 'lp' >>> 片段有很好的缺省值:第一下標(biāo)省略時缺省為零,第二下標(biāo)省略時缺省為字符串的長度。 >>> word[:2] # 前兩個字符 'He' >>> word[2:] # 除前兩個字符串外的部分 'lpA' >>> 注意s[:i] + s[i:] 等于 s 是片段運算的一個有用的恒等式。 >>> word[:2] + word[2:] 'HelpA' >>> word[:3] + word[3:] 'HelpA' >>> 不合理的片段下標(biāo)可以很好地得到解釋:過大的下標(biāo)被換成字符串長度,上界小于下界時返回空串。 >>> word[1:100] 'elpA' >>> word[10:] '' >>> word[2:1] '' >>> 下標(biāo)允許為負(fù)數(shù),這時從右向左數(shù)。例如: >>> word[-1] # 最后一個字符 'A' >>> word[-2] # 倒數(shù)第二個字符 'p' >>> word[-2:] # 最后兩個字符 'pA' >>> word[:-2] # 除最后兩個字符外的部分 'Hel' >>> 但要注意的是 -0 實際還是 0,所以它不會從右向左數(shù)! >>> word[-0] # (因為 -0 等于 0) 'H' >>> 超出范圍的片段下標(biāo)被截斷,但在非片段的情況下不要這樣: >>> word[-100:] 'HelpA' >>> word[-10] # 錯誤 Traceback (innermost last): File "<stdin>", line 1 IndexError: string index out of range >>> 記住片段意義的最好方法是把下標(biāo)看成是字符 之間的點,第一個字符的左邊界號碼為0。有n個字符的字符串的最后一個字符的右邊界下標(biāo)為n,例如: +---+---+---+---+---+ | H | e | l | p | A | +---+---+---+---+---+ 0 1 2 3 4 5 -5 -4 -3 -2 -1 第一行數(shù)字給出字符串中下標(biāo)0到5的位置,第二行給出相應(yīng)的負(fù)下標(biāo)。從i到j(luò)的片段由在邊界i和j之間的字符組成。 對于非負(fù)下標(biāo),如果下標(biāo)都在界內(nèi),則片段的長度為下標(biāo)的差。例如,word[1:3] 的長度為 2。 內(nèi)置函數(shù)len()返回字符串的長度: >>> s = 'supercalifragilisticexpialidocious' >>> len(s) 34 >>> 多行的長字符串也可以用行尾反斜杠續(xù)行,續(xù)行的行首空白不被忽略,如 hello = "This is a rather long string containing\n\ several lines of text just as you would do in C.\n\ Note that whitespace at the beginning of the line is\ significant.\n" print hello 結(jié)果為 This is a rather long string containing several lines of text just as you would do in C. Note that whitespace at the beginning of the line is significant. 對于特別長的字符串(比如包含說明的幾段文字),如果用上面的方式每行都用\n\結(jié)尾是很麻煩的,特別是這樣無法用象Emacs這樣的功能強大的編輯器重新編排。對這種情況,可以使用三重撇號,例如 hello = """ This string is bounded by triple double quotes (3 times "). Unescaped newlines in the string are retained, though \ it is still possible\nto use all normal escape sequences. Whitespace at the beginning of a line is significant. If you need to include three opening quotes you have to escape at least one of them, e.g. \""". This string ends in a newline. """ 三重撇號字符串也可以用三個單撇號,沒有任何語義差別。 多行的字符串常量可以直接連接起來,字符串常量之間用空格分隔則在編譯時可以自動連接起來,這樣可以把一個長字符串連接起來而不需要犧牲縮進對齊或性能,不象用加號連接需要運算,也不象字符串串內(nèi)的換行其行首空格需要保持。 3.3 列表 Python中有幾種復(fù)合數(shù)據(jù)類型,用來把其它值組合到一起。其中最靈活的是列表,可以寫成在方括號之間用逗號隔開的若干值(項)。列表的項不必取同一類型。 >>> a = ['spam', 'eggs', 100, 1234] >>> a ['spam', 'eggs', 100, 1234] >>> 象字符串下標(biāo)那樣,列表下標(biāo)從0開始,列表可以取片段,可以連接,等等: >>> a[0] 'spam' >>> a[3] 1234 >>> a[-2] 100 >>> a[1:-1] ['eggs', 100] >>> a[:2] + ['bacon', 2*2] ['spam', 'eggs', 'bacon', 4] >>> 3*a[:3] + ['Boe!'] ['spam', 'eggs', 100, 'spam', 'eggs', 100, 'spam', 'eggs', 100, 'Boe!'] >>> 與字符串不同的是列表是可變的,可以修改列表的每個元素: >>> a ['spam', 'eggs', 100, 1234] >>> a[2] = a[2] + 23 >>> a ['spam', 'eggs', 123, 1234] >>> 也可以給一個片段重新賦值,這甚至可以改變表的大小: >>> # 替換若干項: ... a[0:2] = [1, 12] >>> a [1, 12, 123, 1234] >>> # 去掉若干項: ... a[0:2] = [] >>> a [123, 1234] >>> # 插入若干項: ... a[1:1] = ['bletch', 'xyzzy'] >>> a [123, 'bletch', 'xyzzy', 1234] >>> a[:0] = a # 在開頭插入自身 >>> a [123, 'bletch', 'xyzzy', 1234, 123, 'bletch', 'xyzzy', 1234] >>> 內(nèi)置函數(shù)也使用于列表: >>> len(a) 8 >>> 可以建立嵌套列表(表的元素也是列表),如: >>> q = [2, 3] >>> p = [1, q, 4] >>> len(p) 3 >>> p[1] [2, 3] >>> p[1][0] 2 >>> p[1].append('xtra') # 列表方法 >>> p [1, [2, 3, 'xtra'], 4] >>> q [2, 3, 'xtra'] >>> 注意這個例子中p[1]和q實際是同一個對象!也就是說它們只不過是同一個東西的兩個名字(引用)而已。 3.4 編程初步 Python當(dāng)然不是只能用來把兩個數(shù)加到一起,它可以完成很復(fù)雜的工作。例如,我們可以寫出Fibonacci序列的開始幾個: >>> # Fibonacci 序列: ... # 兩個元素的和定義下一個 ... a, b = 0, 1 >>> while b < 10: ... print b ... a, b = b, a+b ... 1 1 2 3 5 8 >>> 這個例子介紹了幾個新特色。 第一行包含一個多重賦值: 變量a和b同時得到新值0和1。在最后一行又用了多重賦值,我們可以看出賦值時先把右邊都算出后再進行賦值。 while循環(huán)當(dāng)循環(huán)條件(這里即: b < 10)成立時不斷執(zhí)行。在Python中和C中一樣,非零整數(shù)值為真值,零為假值。條件也可以是字符串或列表或任何序列,長度為非零的為真,空序列為假。例子中所用的是一個簡單比較。標(biāo)準(zhǔn)的比較算符和C一樣: <, >, ==, <=, >= 和 !=。 循環(huán)體是縮進的:縮進是Python用來組合語句的方式。Python目前還不能提供智能自動縮進,所以你需要自己為每個縮進行鍵入制表符或空格。實際使用中你可以用文本編輯程序為Python 準(zhǔn)備復(fù)雜的輸入,多數(shù)文本編輯程序都有自動縮進的功能。在交互輸入復(fù)合語句時必修附加一個空行以指示復(fù)合語句的完成(因為解釋程序無法猜到哪是語句的最后一行)。print語句顯示后面的表達式值。這和直接寫出表達式不同,它可以顯示多個表達式和字符串,而且可以用于程序文件中。顯示時字符串沒有撇號,各項目之間插入一個空格,所以你可以以精美的格式顯示,如: >>> i = 256*256 >>> print 'The value of i is', i The value of i is 65536 >>> 在尾部寫一個逗號可以避免最后換行: >>> a, b = 0, 1 >>> while b < 1000: ... print b, ... a, b = b, a+b ... 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 >>> 注意如果前一行沒有結(jié)束的話系統(tǒng)在顯示提示之前先換行。 Python還提供了和C語言一樣的printf格式的輸出方式,這是用%實現(xiàn)的,左邊是格式,如: >>> print 'The value of 1/7 is approximately %5.3f.' % 0.142857 The value of 1/7 is approximately 0.143. >>> 如果有多個需要輸出的項百分號右邊的項可以是一個序組,如 >>> print "Name: %-10s Age: %3d" % ("White", 31) Name: White Age: 31 第四章 流程控制 前面我們已經(jīng)見到了如何由用while結(jié)構(gòu)控制流程運行。這一章我們介紹更多的控制結(jié)構(gòu)。Python具有和其它語言類似的控制結(jié)構(gòu)但略有差別。 4.1 If 語句 If 語句可能是最基本的程序分支語句了。例如: >>> if x < 0: ... x = 0 ... print 'Negative changed to zero' ... elif x == 0: ... print 'Zero' ... elif x == 1: ... print 'Single' ... else: ... print 'More' ... 可以有零到多個elif部分,else部分可選。關(guān)鍵字elif是else if的縮寫,這樣可以縮短語句行長度。其它語言中switch 或 case 語句可以用if...elif...elif...語句組來實現(xiàn)。 4.2 for 語句 Python中的for語句與你可能熟悉的C或者Pascal中的相應(yīng)語句略有不同。不象Pascal 那樣總是對數(shù)字序列進行循環(huán),也不是象C中那樣完全由程序員自由地控制循環(huán)條件和循環(huán)體,Python的for循環(huán)是對任意種類的序列(如列表或字符串)按出現(xiàn)次序遍歷每一項。 例如: >>> # 計算字符串長: ... a = ['cat', 'window', 'defenestrate'] >>> for x in a: ... print x, len(x) ... cat 3 window 6 defenestrate 12 >>> 盡量不要在循環(huán)體內(nèi)修改用來控制循環(huán)的序列(當(dāng)然,只有可變的序列類型如列表才有可能被修改),這樣程序可能會出問題。如果需要這樣,比如說要復(fù)制某些項,可以用序列的副本來控制循環(huán)。片段記號讓你很容易生成副本: >>> for x in a[:]: # 生成整個列表的片段副本 ... if len(x) > 6: a.insert(0, x) ... >>> a ['defenestrate', 'cat', 'window', 'defenestrate'] >>> 結(jié)果是把列表中長度超過6個字符的字符串插入到列表開頭。 4.3 range() 函數(shù) 如果確實需要對一列數(shù)字進行循環(huán)的話,可以使用內(nèi)置函數(shù)range()。它生成包含數(shù)字序列的列表,如: >>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> 注意給出的終點永遠(yuǎn)不出現(xiàn)在生成的列表中,range(10)生成一個十個數(shù)的列表,恰好是長度為10的序列的合法下標(biāo)的各個值。也可以指定不同的起始點,或者指定不同的間隔(甚至負(fù)數(shù)): >>> range(5, 10) [5, 6, 7, 8, 9] >>> range(0, 10, 3) [0, 3, 6, 9] >>> range(-10, -100, -30) [-10, -40, -70] >>> 為了對序列的下標(biāo)進行循環(huán),如下聯(lián)合使用range() 和 len(): >>> a = ['Mary', 'had', 'a', 'little', 'lamb'] >>> for i in range(len(a)): ... print i, a[i] ... 0 Mary 1 had 2 a 3 little 4 lamb >>> 4.4 break語句,continue語句和循環(huán)中的else子句 如同C語言一樣,break語句跳出其所處的最內(nèi)層的for 或 while循環(huán),continue語句繼續(xù)下一循環(huán)步。 循環(huán)語句還可以帶一個 else 子句,當(dāng)循環(huán)正常結(jié)束時執(zhí)行其內(nèi)容,但如果循環(huán)是用break 語句跳出的則不執(zhí)行其內(nèi)容。下例說明了這種用法,此例求素數(shù): >>> for n in range(2, 10): ... for x in range(2, n): ... if n % x == 0: ... print n, 'equals', x, '*', n/x ... break ... else: ... print n, 'is a prime number' ... 2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3 >>> 4.5 pass 語句 pass語句不執(zhí)行任何操作,當(dāng)語法要求一個語句而程序不需要執(zhí)行操作時就用此語句。 例如: >>> while 1: ... pass # 等待鍵盤中斷 ... 4.6 函數(shù)定義 我們可以定義一個函數(shù)用來計算某一界限以下的所有Fibonacci序列值: >>> def fib(n): # 寫出 n 以下的所有Fibonacci序列值 ... a, b = 0, 1 ... while b < n: ... print b, ... a, b = b, a+b ... >>> # 調(diào)用剛剛定義的函數(shù): ... fib(2000) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 >>> 其中關(guān)鍵字 def 開始一個函數(shù)定義,其后應(yīng)該是函數(shù)名,括號內(nèi)的形參表,以冒號結(jié)束。構(gòu)成函數(shù)體的各語句從下一行開始,用一個制表符縮進。函數(shù)的第一個語句可以是一個字符串,如果是的話,這個字符串就是函數(shù)的文檔字符串,簡稱為docstring。有一些工具可以利用文檔字符串自動生成可打印的文檔,或者讓用戶交互地瀏覽代碼,所以在自己編程時加入文檔字符串是一個好習(xí)慣,應(yīng)該養(yǎng)成這樣的習(xí)慣。 函數(shù)在執(zhí)行時對局部變量引入一個新的符號表。函數(shù)中的變量賦值都存入局部符號表;引用變量時變量名先從局部符號表中查找,然后在全局符號表中查找,最后從內(nèi)置的名字中查找。因此,在函數(shù)中不能直接對全局變量賦值(除非用了global語句來說明),但可以引用全局變量的值。 函數(shù)調(diào)用的實參被引入函數(shù)的局部符號表,即函數(shù)的參數(shù)是按值調(diào)用的。函數(shù)再調(diào)用其它函數(shù)時為該函數(shù)生成一個新的符號表。但是嚴(yán)格地說,函數(shù)的調(diào)用是按引用調(diào)用的,因為如果參數(shù)是一個可變類型如列表的話在函數(shù)中改變形參的內(nèi)容將導(dǎo)致實參的內(nèi)容被改變(不改變的是實參名字的綁定關(guān)系)。 函數(shù)定義把函數(shù)名放入當(dāng)前符號表。函數(shù)名的值類型為用戶自定義函數(shù),這個值可以賦給另一個名字,從而這個名字也代表相同的函數(shù)。這可以作為一般的改名方法: >>> fib <function object at 10042ed0> >>> f = fib >>> f(100) 1 1 2 3 5 8 13 21 34 55 89 >>> 你可能會說 fib 不是函數(shù)而是過程。Python和C一樣,過程只是不返回值的函數(shù)。實際上,嚴(yán)格地說,過程也返回一個值,只不過是一個很沒意思的值。這個值叫做 None(這是一個內(nèi)置的名字)。解釋程序交互運行時如果只需要顯示這個值的話就會忽略不顯示。如果希望顯示的話可以用 print 語句: >>> print fib(0) None >>> 也可以寫一個函數(shù)返回Fibonacci 序列的數(shù)值列表而不是顯示這些值: >>> def fib2(n): # 返回直到n的Fibonacci 序列值 ... result = [] ... a, b = 0, 1 ... while b < n: ... result.append(b) # 解釋見下面 ... a, b = b, a+b ... return result ... >>> f100 = fib2(100) # 調(diào)用 >>> f100 # 輸出結(jié)果 [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] >>> 這個例子也演示了新的Python特色:return語句從函數(shù)中退出并返回一個值。不帶返回值的return可以從過程中間退出,運行到過程的末尾也可以退出,這兩種情況下返回None。 語句result.append(b)調(diào)用列表對象result的一個方法。方法是“屬于”一個對象的函數(shù),引用格式為obj.methodname,其中obj是某個對象(也允許是一個表達式), methodname 是由該對象的類型定義的一個方法的名字。不同的不同的方法。不同類型的方法可以使用相同的名字而不致引起誤解。(可以定義自己的對象類型和方法,使用類,本文后面會討論這個話題)。例子中的append()方法時列表對象的方法,它在列表末尾增加一個新元素。在本例中這等價于“result = result + ”,只是更有效。 4.7 函數(shù)參數(shù) 可以定義使用可變個數(shù)參數(shù)的函數(shù)。這樣的定義方法有三種,可以聯(lián)合使用。 4.7.1 參數(shù)缺省值 可以為一個參數(shù)或幾個參數(shù)指定缺省值。這樣定義的函數(shù)在調(diào)用時實參個數(shù)可以比定義時少。例如: def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): while 1: ok = raw_input(prompt) if ok in ('y', 'ye', 'yes'): return 1 if ok in ('n', 'no', 'nop', 'nope'): return 0 retries = retries - 1 if retries < 0: raise IOError, 'refusenik user' print complaint 這個函數(shù)在調(diào)用時既可以這樣調(diào)用:ask_ok('Do you really want to quit?'),或者可以這樣調(diào)用:ask_ok('OK to overwrite the file?', 2)。缺省值是在函數(shù)定義時的定義作用域中計算的,所以例如: i = 5 def f(arg = i): print arg i = 6 f() 將顯示5。 注意:缺省值只計算一次。當(dāng)缺省值是可變對象如列表或字典時這一點是要注意的。例如,以下函數(shù)會在以后的調(diào)用中累加它的值: def f(a, l = []): l.append(a) return l print f(1) print f(2) print f(3) This will print [1] [1, 2] [1, 2, 3] 如果你不希望缺省值在連續(xù)的調(diào)用中被保留,可以象下面這樣改寫函數(shù): def f(a, l = None): if l is None: l = [] l.append(a) return l 4.7.2 關(guān)鍵字參數(shù) 函數(shù)調(diào)用時也可以象“關(guān)鍵字 = 值”這樣指定實參,其中關(guān)鍵字是定義時使用的形參的名字。例如: def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'): print "-- This parrot wouldn't", action, print "if you put", voltage, "Volts through it." print "-- Lovely plumage, the", type print "-- It's", state, "!" 可以用如下幾種方式調(diào)用: parrot(1000) # 缺省值 parrot(action = 'VOOOOOM', voltage = 1000000) # 關(guān)鍵字,缺省值,次序可變 parrot('a thousand', state = 'pushing up the daisies') # 位置參數(shù),缺省值, 關(guān)鍵字 parrot('a million', 'bereft of life', 'jump') # 位置參數(shù),缺省值 但以下幾種調(diào)用方式是錯誤的: parrot() # 非缺省的參數(shù)沒有提供 parrot(voltage=5.0, 'dead') # 關(guān)鍵字參數(shù)后面又出現(xiàn)了非關(guān)鍵字參數(shù) parrot(110, voltage=220) # 參數(shù)值重復(fù)提供 parrot(actor='John Cleese') # 未知關(guān)鍵字 一般說來,實參表中位置參數(shù)在前,關(guān)鍵字參數(shù)在后,關(guān)鍵字名字必須是形參名字。形參有沒有缺省值都可以用關(guān)鍵字參數(shù)的形式調(diào)用。每一形參至多只能對應(yīng)一個實參,因此,已經(jīng)由位置參數(shù)傳入值的形參就不能在同一調(diào)用中再作為關(guān)鍵字參數(shù)。 如果形參表中有一個形為**name的形參,在調(diào)用時這個形參可以接收一個字典,字典中包含所有不與任何形參匹配的關(guān)鍵字參數(shù)。形參表中還可以使用一個特殊的如*name的形參,它將接受所有不能匹配的位置參數(shù)組成的一個序表。*name只能在**name之前出現(xiàn)。例如,如果定義了下面的函數(shù): def cheeseshop(kind, *arguments, **keywords): print "-- Do you have any", kind, '?' print "-- I'm sorry, we're all out of", kind for arg in arguments: print arg print '-'*40 for kw in keywords.keys(): print kw, ':', keywords[kw] 就可以象下面這樣調(diào)用: cheeseshop('Limburger', "It's very runny, sir.", "It's really very, VERY runny, sir.", client='John Cleese', shopkeeper='Michael Palin', sketch='Cheese Shop Sketch') 結(jié)果顯示: -- Do you have any Limburger ? -- I'm sorry, we're all out of Limburger It's very runny, sir. It's really very, VERY runny, sir. ---------------------------------------- client : John Cleese shopkeeper : Michael Palin sketch : Cheese Shop Sketch 4.7.3 任意個數(shù)參數(shù) 在所有有名的形參的后面可以有兩個特殊的形參,一個以*args的形式命名,一個以**kw 的形式命名。有了*args形式的形參后函數(shù)在調(diào)用時就可以在正常的能匹配的實參表后面輸入任意個數(shù)的參數(shù),這些參數(shù)組成一個序表賦給args形參,不能匹配的關(guān)鍵字參數(shù)組成一個字典賦給kw形參。在任意個數(shù)形參之前可以有0到多個正常的參數(shù)。例如: def fprintf(file, format, *args): file.write(format % args) 4.7.4 Lambda形式 因為許多人的要求,Python中加入了一些在函數(shù)編程語言和Lisp中常見的功能。可以用lambda 關(guān)鍵字來定義小的無名函數(shù)。這是一個返回其兩個參數(shù)的和的函數(shù):“l(fā)ambda a, b: a+b” 。Lambda形式可以用于任何需要函數(shù)對象的地方。從句法上講lambda形式局限于一個表達式。從語義上講,這只是正常的函數(shù)定義的句法甜食。像嵌套函數(shù)定義一樣,lambda形式不能訪問包含其定義的作用域中的變量,但審慎地使用缺省參數(shù)之可以繞過這個限制。例如: def make_incrementor(n): return lambda x, incr=n: x+incr 4.7.5 文檔字符串 關(guān)于文檔字符串的內(nèi)容與格式正在形成一些慣例。第一行應(yīng)該為簡短的對象目的概括說明。為了簡明起見,這一行不應(yīng)該提及對象的名字或類型,因為這些可以通過其他途徑得知(當(dāng)然如果對象名字就是一個描述函數(shù)操作的動詞則當(dāng)然可以提及其名字)。著以行應(yīng)該用大些字母開始,以句點結(jié)尾。如果文檔字符串中有多行,第二行應(yīng)該是空行,把概括說明與其它說明分開。以下的行可以是一段或幾段,描述對象的調(diào)用方法,它的副作用,等等。 Python的掃描程序不會從多行字符串中去掉縮進空白,所以處理文檔的工具需要自己處理縮進。只要遵循如下的慣例就可以有利于縮進空白的處理。在第一行之后的第一個非空白的行決定整個文檔字符串的縮進數(shù)量(我們不用第一行,因為它經(jīng)常是直接跟在表示字符串開始的引號后面)。文檔字符串中除第一行以外的各行都要刪除等價于此行的縮進量的空白。對制表符將擴展為空格后再刪除。 [b]第五章 Python數(shù)據(jù)結(jié)構(gòu) 本章更詳細(xì)地討論一些已經(jīng)講過的數(shù)據(jù)類型的使用,并引入一些新的類型。 5.1 列表 列表數(shù)據(jù)類型還有其它一些方法。下面是列表對象的所有方法: insert(i, x) ---- 在指定位置插入一項。第一自變量是要在哪一個元素前面插入,用下標(biāo)表示。例如,a.insert(0, x)在列表前面插入,a.insert(len(a), x)等價于a.append(x) 。 append(x) ---- 等價于a.insert(len(a), x) index(x) ---- 在列表中查找值x然后返回第一個值為x的元素的下標(biāo)。沒有找到時出錯。 remove(x) ---- 從列表中刪去第一個值為x的元素,找不到時出錯。 sort() ---- 對列表元素在原位排序。注意這個方法改變列表,而不是返回排序后的列表。 reverse() ---- 把列表元素反序。改變列表。 count(x) ---- 返回x在列表中出現(xiàn)的次數(shù)。 下例使用了所有的列表方法: >>> a = [66.6, 333, 333, 1, 1234.5] >>> print a.count(333), a.count(66.6), a.count('x') 2 1 0 >>> a.insert(2, -1) >>> a.append(333) >>> a [66.6, 333, -1, 333, 1, 1234.5, 333] >>> a.index(333) 1 >>> a.remove(333) >>> a [66.6, -1, 333, 1, 1234.5, 333] >>> a.reverse() >>> a [333, 1234.5, 1, 333, -1, 66.6] >>> a.sort() >>> a [-1, 1, 66.6, 333, 333, 1234.5] 5.1.1 函數(shù)程序設(shè)計工具 Python中有一些函數(shù)程序設(shè)計風(fēng)格的東西,例如前面我們看到的lambda形式。關(guān)于列表有三個非常有用的內(nèi)置函數(shù):filter(), map()和reduce()。 “filter(函數(shù), 序列)”返回一個序列(盡可能與原來同類型),序列元素是原序列中由指定的函數(shù)篩選出來的那些,篩選規(guī)則是“函數(shù)(序列元素)=true”。filter()可以用來取出滿足條件的子集。例如,為了計算一些素數(shù): >>> def f(x): return x % 2 != 0 and x % 3 != 0 ... >>> filter(f, range(2, 25)) [5, 7, 11, 13, 17, 19, 23] “map(函數(shù),序列)”對指定序列的每一項調(diào)用指定的函數(shù),結(jié)果為返回值組成的列表。 map() 可以對序列進行隱式循環(huán)。例如,要計算三次方,可用: >>> def cube(x): return x*x*x ... >>> map(cube, range(1, 11)) [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000] 可以有多個序列作為自變量,這時指定的函數(shù)必須也有相同個數(shù)的自變量,函數(shù)從每個序列分別取出對應(yīng)元素作為自變量進行調(diào)用(如果某個序列比其它的短則取出的值是None)。如果指定的函數(shù)是None,map()把它當(dāng)成一個返回自己的自變量的恒同函數(shù)。在函數(shù)用None的情況下指定多個序列可以把多個序列搭配起來,比如“map(None, list1, list2)”可以把兩個列表組合為一個成對值的列表。見下例: >>> seq = range(8) >>> def square(x): return x*x ... >>> map(None, seq, map(square, seq)) [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25), (6, 36), (7, 49)] “reduce(函數(shù), 序列)”用來進行類似累加這樣的操作,這里的函數(shù)是一個兩個子變量的函數(shù),reduce()先對序列的前兩項調(diào)用函數(shù)得到一個結(jié)果,然后對結(jié)果和序列下一項調(diào)用函數(shù)得到一個新結(jié)果,如此進行到序列尾部。例如,要計算1到10的和: >>> def add(x,y): return x+y ... >>> reduce(add, range(1, 11)) 55 如果序列中只有一個值則返回這個值,序列為空時會產(chǎn)生例外。可以指定第三個自變量作為初始值。有初始值時對空序列函數(shù)將返回初始值,否則函數(shù)先對初始值和序列第一項作用,然后對結(jié)果和序列下一項作用,如此進行到序列尾。例如: >>> def sum(seq): ... def add(x,y): return x+y ... return reduce(add, seq, 0) ... >>> sum(range(1, 11)) 55 >>> sum([]) 0 5.2 del語句 上面我們看到,列表的remove()方法可以從列表中刪去某個取值的項,我們還可以用del 語句來刪除指定下標(biāo)的項。也可以用del語句從列表中刪除一個片斷(前面我們是用給片斷賦空列表的辦法刪除片斷的)。例如: >>> a [-1, 1, 66.6, 333, 333, 1234.5] >>> del a[0] >>> a [1, 66.6, 333, 333, 1234.5] >>> del a[2:4] >>> a [1, 66.6, 1234.5] del也可以用來刪除整個變量,例如: >>> del a 變量刪除以后再引用該變量就會出錯(除非又給它賦值了)。后面我們還會看到del的其它一些應(yīng)用。 5.3 序表和序列 我們看到列表和字符串有許多共同點,例如,下標(biāo)和片斷運算。它們都屬于序列數(shù)據(jù)類型。因為Python是一個正在不斷發(fā)展的語言,以后還可能會加入其它的序列數(shù)據(jù)類型。現(xiàn)在還有一種標(biāo)準(zhǔn)的序列數(shù)據(jù)類型,稱為序表(tuple)。 序表由一系列值用逗號分隔而成,例如: >>> t = 12345, 54321, 'hello!' >>> t[0] 12345 >>> t (12345, 54321, 'hello!') >>> # 序表允許嵌套: ... u = t, (1, 2, 3, 4, 5) >>> u ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5)) 輸出的序表總是用括號包圍,這樣可以保證嵌套序表得以正確解釋。輸入時可以有括號也可以沒有括號,當(dāng)經(jīng)常是必須有括號(如果序表是一個大表達式的一部分)。 序表有許多用處,例如,(x,y)坐標(biāo)對,數(shù)據(jù)庫中的職工紀(jì)錄,等等。序表與字符串一樣是不可變的:不允許對序表的某一項賦值。 生成序表時對0項或1項的序表有特殊的規(guī)定:空序表用一對空括號表示;只有一項的序表用一個之后面跟一個抖好表示(指把這個值放在括號內(nèi)是不夠的)。這樣寫不夠美觀,但很有效。例如: >>> empty = () >>> singleton = 'hello', # <-- note trailing comma >>> len(empty) 0 >>> len(singleton) 1 >>> singleton ('hello',) 語句t = 12345, 54321, 'hello!'是序表打包的一個實例:12345, 54321和'hello!'這些值被打包進了一個序表中。相反的操作也是允許的,例如: >>> x, y, z = t 這叫做序表解包。序表解包要求等號左邊的變量個數(shù)等于序表的長度。注意多重賦值只是序表打包和序表解包的聯(lián)合使用。有時也對列表進行類似操作,即列表解包。只要把各變量寫成一個列表就可以進行解包: >>> a = ['spam', 'eggs', 100, 1234] >>> [a1, a2, a3, a4] = a 5.4 字典 Python內(nèi)置的另一個有用的數(shù)據(jù)類型是字典。字典在其它語言中有時被稱為“關(guān)聯(lián)記憶” 或“關(guān)聯(lián)數(shù)組”。字典不象序列,它不是用在一個范圍之內(nèi)的數(shù)字下標(biāo)來索引,而是用鍵值來索引,鍵值可以是任何不可變類型。字符串和數(shù)值總可以作鍵值。如果序表只包含字符串、數(shù)值或序表則序表也可以作鍵值使用。列表不能用作鍵值,因為列表可以用其append()方法就地改變值。 最好把字典看成是一系列未排序的“鍵值:值”的集合,在同一字典內(nèi)鍵值是互不相同的。一對空大括號產(chǎn)生一個空字典:{}。在大括號內(nèi)加入用逗號分開的“鍵值:值”對可以在字典內(nèi)加入初始的鍵值和值對,字典在輸出時也是這樣顯示的。對字典的主要操作是以某個鍵值保存一個值,以及給定鍵值后查找對應(yīng)的值。也可以用del刪除某個鍵值:值對。如果用一個已有定義的鍵值保存某個值則原來的植被遺忘。用不存在的鍵值去查找會出錯。 字典對象的keys()方法返回字典中所有鍵值組成的列表,次序是隨機的。需要排序時只要對返回的鍵值列表使用sort()方法。為了檢查某個鍵值是否在字典中,使用字典的has_key() 方法。 下面是字典使用的一個簡單例子: >>> tel = {'jack': 4098, 'sape': 4139} >>> tel['guido'] = 4127 >>> tel {'sape': 4139, 'guido': 4127, 'jack': 4098} >>> tel['jack'] 4098 >>> del tel['sape'] >>> tel['irv'] = 4127 >>> tel {'guido': 4127, 'irv': 4127, 'jack': 4098} >>> tel.keys() ['guido', 'irv', 'jack'] >>> tel.has_key('guido') 1 5.5 條件的進一步討論 在while語句和if語句中使用的條件除了可以使用比較之外還可以包含其它的運算符。比較運算符“in”和“not in”可以檢查一個值是否在一個序列中。運算符“is”和“is not ”比較兩個對象是否恰好是同一個對象,這只對象列表這樣的可變對象有意義。所有比較運算優(yōu)先級相同,而比較運算的優(yōu)先級比所有數(shù)值運算優(yōu)先級低。 比較允許連寫,例如,a < b == c檢查是否a小于等于b而且b等于c。 比較可以用邏輯運算符and和or連接起來,比較的結(jié)果(或其它任何邏輯表達式)可以用not 取反。邏輯運算符又比所有比較運算符低,在邏輯運算符中,not優(yōu)先級最高,or的優(yōu)先級最低,所以“A and not B or C”應(yīng)解釋為“(A and (not B)) or C”。當(dāng)然,可以用括號來表示所需的組合條件。 邏輯運算符and和or稱為“短路”運算符:運算符兩側(cè)的表達式是先計算左邊的,如果左邊的結(jié)果已知則整體結(jié)果已知就不再計算右邊的表達式。例如,如果A和C為真而B為假則“A and B and C”不會計算表達式C。一般地,當(dāng)短路運算符的運算結(jié)果不是用作邏輯值的時候返回的是最后求值的那個表達式的值。 可以把比較或其它邏輯表達式的結(jié)果賦給一個變量。例如: >>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance' >>> non_null = string1 or string2 or string3 >>> non_null 'Trondheim' 注意Python和C不同,表達式中不能進行賦值。 5.6 序列與其它類型的比較 序列對象可以和其它同序列類型的對象比較。比較使用字典序:先比較最前面兩項,如果這兩項不同則結(jié)果可以確定;如果這兩項相同,就比較下面的兩項,如此下去,直到有一個序列到頭為止。如果某兩項本身也是同類型的序列,則進行遞歸的字典序比較。如果兩個序列的所有各項都相等,則這兩個序列相等。如果一個序列是另一個序列的一個初始子序列,短的一個是較小的一個。字符串的字典序比較按各個字符的ASCII次序進行。下面是一些序列比較的實例: (1, 2, 3) < (1, 2, 4) [1, 2, 3] < [1, 2, 4] 'ABC' < 'C' < 'Pascal' < 'Python' (1, 2, 3, 4) < (1, 2, 4) (1, 2) < (1, 2, -1) (1, 2, 3) = (1.0, 2.0, 3.0) (1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4) 注意不同類型的對象比較目前也是合法的。結(jié)果是確定的但卻沒有什么意義:不同類型是按類型的名字排序的。所以,列表(list)總是小于字符串(string),字符串總是小于序表(tuple),等等。但是程序中不能依賴這樣的比較規(guī)則,語言實現(xiàn)可能會改變。不同的數(shù)值類型可以按數(shù)值來比較,所以0等于0.0,等等。 第六章 模塊 如果退出Python解釋程序然后再進入,原有的定義(函數(shù)和變量)就丟失了。所以,如果需要寫長一點的程序,最好用一個文本編輯程序為解釋程序準(zhǔn)備輸入,然后以程序文件作為輸入來運行Python解釋程序,這稱為準(zhǔn)備腳本(script)。當(dāng)你的程序變長時,最好把它拆分成幾個文件以利于維護。你還可能想在幾個程序中都使用某個很方便的函數(shù),但又不想把函數(shù)定義賦值到每一個程序中。 為了支持這些,Python有一種辦法可以把定義放在一個文件中然后就可以在一個腳本中或交互運行中調(diào)用。這樣的文件叫做一個模塊;模塊中的定義可以導(dǎo)入其它模塊或主模塊(主模塊指在解釋程序頂級執(zhí)行的腳本或交互執(zhí)行的程序所能訪問的變量集合)。模塊是包含了Python定義和語句的文件。文件名由模塊名加上后綴“.py”構(gòu)成。在模塊內(nèi),模塊的名字(作為一個字符串)可以由全局變量__name__的值獲知。例如,在Python的搜索路徑中用你習(xí)慣使用的文本編輯器(Python 1.5.2包含了一個用Tkinter編寫的IDLE集成開發(fā)環(huán)境,MS Windows下有一個PythonWin界面也可以進行Python程序編輯)生成一個名為“fibo.py ”的文件,包含如下內(nèi)容: # Fibonacci numbers module def fib(n): # 輸出小于n的Fibonacci序列 a, b = 0, 1 while b < n: print b, a, b = b, a+b def fib2(n): # 返回小于n的Fibonacci序列 result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result 然后進入Python解釋程序(在IDLE或PythonWin中可以直接進入解釋程序窗口),用如下命令可以導(dǎo)入模塊: >>> import fibo 這不會把模塊fibo中的函數(shù)的名字直接引入當(dāng)前的符號表,這只是把模塊名fibo引入。 可以用模塊名來訪問其中的函數(shù): >>> fibo.fib(1000) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 >>> fibo.fib2(100) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] >>> fibo.__name__ 'fibo' 如果經(jīng)常使用某個函數(shù)可以給它賦一個局部名字: >>> fib = fibo.fib >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 6.1 模塊的進一步介紹 模塊除了可以包含函數(shù)定義之外也可以包含可執(zhí)行語句。這些可執(zhí)行語句用來初始化模塊,它們只在模塊第一次被導(dǎo)入時執(zhí)行。 每個模塊有自己私有的符號表,這個私有符號表對于模塊中的所有函數(shù)而言卻是它們的全局符號表。因此,模塊作者可以在模塊中使用全局變量而不需擔(dān)心與模塊用戶的全局變量沖突。另一方面,如果你有把握的話也可以用訪問模塊中函數(shù)的格式,即modname.itemname的方法來修改模塊中的全局變量。 模塊可以導(dǎo)入其它模塊。我們通常把所有的導(dǎo)入語句放在模塊(或腳本)的開始位置, 這不是規(guī)定要求的。導(dǎo)入的模塊名放入模塊的全局符號表中。 導(dǎo)入還有另一種用法,可以把模塊中的名字直接導(dǎo)入使用者的符號表。例如: >>> from fibo import fib, fib2 >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 這不會把模塊名導(dǎo)入使用者的符號表中(例如,上面例子中fibo就沒有定義)。 還有一種辦法可以導(dǎo)入一個模塊中定義的所有名字: >>> from fibo import * >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 這可以把模塊中除了以下劃線結(jié)尾的所有名字導(dǎo)入。 6.1.1 模塊搜索路徑 在導(dǎo)入名為spam的模塊時,解釋程序先在當(dāng)前目錄中尋找名為“spam.py”的文件,然后從環(huán)境變量PYTHONPATH所定義的目錄列表中尋找。PYTHONPATH的用法和可執(zhí)行文件的搜索路徑PATH用法相同,都是一個目錄列表。當(dāng)PYTHONPATH未設(shè)置的時候,或者文件仍找不到,則搜索繼續(xù)在安裝時設(shè)定的缺省路徑搜索,在Unix中,這通常是“.:/usr/local/lib/python” 。 實際上,模塊是按變量sys.path指定的路徑搜索的,此變量在解釋程序啟動時初始化為包含輸入腳本的目錄(或當(dāng)前路徑),PYTHONPATH和安裝缺省路徑。這樣,用戶可以通過修改sys.path 來修改和替換模塊搜索路徑。參見后面關(guān)于標(biāo)準(zhǔn)模塊的一節(jié)。 6.1.2 “編譯”的Python文件 為了提高調(diào)用許多標(biāo)準(zhǔn)模塊的小程序的啟動時間,一個重要的措施是,如果在找到“spam.py ”的目錄中存在一個名為“spam.pyc”的文件,就認(rèn)為此文件包含了模塊spam的一個所謂“ 字節(jié)編譯”版本。用于生成“spam.pyc”的“spam.py”的修改時間被記入了“spam.pyc”中,如果記錄的修改時間與現(xiàn)在文件的時間不相符的話就忽略編譯文件。 一般不需要自己生成“spam.pyc”這樣的編譯文件。每當(dāng)“spam.py”成功編譯后解釋程序就嘗試寫編譯版本“spam.pyc”,如果不可寫也不會出錯;如果因為某種原因此文件沒有寫完則生成的“spam.pyc”被識別為不完整的而被忽略。編譯文件“spam.pyc”的格式是不依賴于平臺的,所以不同結(jié)構(gòu)的機器可以共享Python模塊目錄。 下面是對專家的一些竅門: 如果Python解釋程序是以-O標(biāo)志啟動的,將生成優(yōu)化的編譯代碼,保存在“.pyo”文件中。目前優(yōu)化不是很多,現(xiàn)在只是去掉assert語句和SET_LINENO指令。使用了-O標(biāo)志時,所有字節(jié)碼都是優(yōu)化的,“.pyc”文件被忽略,“.py”文件被編譯為優(yōu)化的字節(jié)碼。 給Python解釋程序兩個優(yōu)化標(biāo)志(-OO)產(chǎn)生的優(yōu)化代碼有時會導(dǎo)致程序運行不正常。目前雙重優(yōu)化只從字節(jié)碼中刪除了__doc__字符串,使得“.pyo”文件較小。有些程序可能是依賴于文檔字符串的,所以只有在確知不會有問題時才可以使用這樣的優(yōu)化。 從“.pyc”或“.pyo”讀入的程序并不能比從“.py”讀入的運行更快,它們只是調(diào)入速度更快一些。如果一個程序是用在命令行指定腳本文件名的方式運行的,腳本的字節(jié)碼不會寫入“.pyc ”或“.pyo”文件。所以如果把程序的主要代碼都移入一個模塊,腳本中只剩下導(dǎo)入該模塊的引導(dǎo)程序則可以略微縮短腳本的啟動時間。 可以有叫做“spam.pyc”(當(dāng)用了-O標(biāo)志時為“spam.pyo”)的文件而沒有對應(yīng)的源文件“spam.py”。這可以用來分發(fā)一個比較難反編譯的Python代碼庫。 模塊compileall可以把一個目錄中所有模塊編譯為“.pyc”文件(指定了-O選項時編譯為“.pyo”文件)。 6.2 標(biāo)準(zhǔn)模塊 Python帶有一個標(biāo)準(zhǔn)模塊庫,在另一個文檔《Python庫參考》中進行了描述。一些模塊直接編入了解釋程序中,這些模塊不是語言的核心,為了運行效率或者為了提供對于系統(tǒng)調(diào)用這樣的系統(tǒng)底層功能而編入了解釋程序中。提供那些模塊是編譯時的選擇,例如,amoeba模塊只在提供amoeba底層指令的系統(tǒng)中才能提供。 有一個模塊值得特別重視:sys模塊,每一個Python解釋程序中都編譯入了這個模塊。變量sys.ps1和sys.ps2定義了交互運行時的初始提示和續(xù)行提示。 >>> import sys >>> sys.ps1 '>>> ' >>> sys.ps2 '... ' >>> sys.ps1 = 'C> ' C> print 'Yuck!' Yuck! C> 這兩個變量只在解釋程序以交互方式運行時才有定義。 變量sys.path是一個字符串列表,由它確定解釋程序的模塊搜索路徑。它被初始化為環(huán)境變量PYTHONPATH所指定的缺省路徑,環(huán)境變量沒有定義時初始化為安裝時的缺省路徑。可以用標(biāo)準(zhǔn)的列表操作修改這個搜索路徑,例如: >>> import sys >>> sys.path.append('/ufs/guido/lib/python') 6.3 dir()函數(shù) 內(nèi)置函數(shù)dir()用于列出一個模塊所定義的名字,它返回一個字符串列表: >>> import fibo, sys >>> dir(fibo) ['__name__', 'fib', 'fib2'] >>> dir(sys) ['__name__', 'argv', 'builtin_module_names', 'copyright', 'exit', 'maxint', 'modules', 'path', 'ps1', 'ps2', 'setprofile', 'settrace', 'stderr', 'stdin', 'stdout', 'version'] 沒有自變量時,dir()列出當(dāng)前定義的名字。 >>> a = [1, 2, 3, 4, 5] >>> import fibo, sys >>> fib = fibo.fib >>> dir() ['__name__', 'a', 'fib', 'fibo', 'sys'] 注意dir()列出了所有各類名字:變量名、模塊名、函數(shù)名,等等。dir()不會列出內(nèi)置函數(shù)、變量的名字。要想列出內(nèi)置名字的話需要使用標(biāo)準(zhǔn)模塊__builtin__: >>> import __builtin__ >>> dir(__builtin__) ['AccessError', 'AttributeError', 'ConflictError', 'EOFError', 'IOError', 'ImportError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'MemoryError', 'NameError', 'None', 'OverflowError', 'RuntimeError', 'SyntaxError', 'SystemError', 'SystemExit', 'TypeError', 'ValueError', 'ZeroDivisionError', '__name__', 'abs', 'apply', 'chr', 'cmp', 'coerce', 'compile', 'dir', 'divmod', 'eval', 'execfile', 'filter', 'float', 'getattr', 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'len', 'long', 'map', 'max', 'min', 'oct', 'open', 'ord', 'pow', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'round', 'setattr', 'str', 'type', 'xrange'] 6.4 包 Python中可以用“包”來組織Python的模塊名字空間,名字引用時可以用“帶點的模塊名。例如,模塊名A.B代表包“A”內(nèi)名為“B”的子模塊。正如使用模塊可以使不同模塊的作者不用顧慮彼此的全局變量名會沖突,使用帶點的模塊名可以使多模塊包如NumPy和PIL的作者不需要擔(dān)心彼此的模塊名會沖突。 假設(shè)你有一系列處理聲音文件和聲音數(shù)據(jù)的模塊(稱為一個“包”)。有許多種不同的聲音文件格式(通常用擴展名來識別,如“wav”,“.aiff”,“.au”),所以你可能需要制作并維護一組不斷增加的模塊來處理不同文件格式的轉(zhuǎn)換。你還可能需要對聲音數(shù)據(jù)進行許多不同的操作(如混音、回響、均衡、產(chǎn)生模擬立體聲效果),所以你還需要不斷增加模塊來執(zhí)行這些操作。一下是你的程序包的可能的結(jié)構(gòu)(用一個分層文件系統(tǒng)表示): Sound/ 頂層包 __init__.py 初始化音響包 Formats/ 用于文件格式轉(zhuǎn)換的子程序包 __init__.py wavread.py wavwrite.py aiffread.py aiffwrite.py auread.py auwrite.py ... Effects/ 用于音響效果的子程序包 __init__.py echo.py surround.py reverse.py ... Filters/ 用于濾波的子程序包 __init__.py equalizer.py vocoder.py karaoke.py ... 包目錄中的“__init__.py”文件是必須得,用來指示Python把這個目錄看成包,這可以防止有相同名字如“string”的子目錄掩蓋住在搜索路徑后面一些出現(xiàn)的模塊定義。在最簡單的情況下,“__init__.py”可以是一個空文件,它也可以包含初始化包所需的代碼,和設(shè)置“__all__”變量,這些后面會加以討論。 包的用戶可以從包中導(dǎo)入單獨的模塊,如: import Sound.Effects.echo 這可以把子模塊Sound.Effects.echo導(dǎo)入。要引用它也必須用全名,例如: Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4) 導(dǎo)入子模塊的另一種辦法是: from Sound.Effects import echo 這同樣也導(dǎo)入子模塊echo,但調(diào)用時不需寫包前綴,所以可以用如: echo.echofilter(input, output, delay=0.7, atten=4) 另外一種寫法是直接導(dǎo)入所需的函數(shù)或變量: from Sound.Effects.echo import echofilter 這一次同樣是調(diào)入了子模塊echo,但是使其函數(shù)echofilter直接可用: echofilter(input, output, delay=0.7, atten=4) 注意使用“from 包 import 項”這樣的格式時,導(dǎo)入的項可以是包的一個子模塊(或子包),也可以是包內(nèi)定義的其它名字如函數(shù)、類、變量。導(dǎo)入語句首先查找包內(nèi)是否定義了所需的項,如果沒有則假設(shè)它是一個模塊然后調(diào)入。如果找不到,結(jié)果引起ImportError。 相反的,當(dāng)使用“import item.subitem.subsubitem”這樣的格式時,除最后一個外其它各項都應(yīng)該是包,最后一項可以是包也可以是模塊,不允許是前面一項內(nèi)部定義的類、函數(shù)或變量。 6.4.1 從包中導(dǎo)入* 現(xiàn)在,如果用戶寫“from Sound.Effects import *”會發(fā)生什么情況?理想情況下我們希望這應(yīng)該掃描文件系統(tǒng),找到所有包內(nèi)的子模塊并把它們都導(dǎo)入進來。不幸的是這種操作在Mac和Windows平臺上不能準(zhǔn)確實現(xiàn),這兩種操作系統(tǒng)對文件名的大小寫沒有準(zhǔn)確信息。在這些平臺上,不知道名為“ECHO.PY”的文件會作為模塊echo、Echo還是ECHO被導(dǎo)入。(例如,Windows 95在顯示文件名時總是討厭地把第一個字母大寫)。DOS的8+3文件名限制更是對長模塊名造成了有趣的困難。 這個問題的唯一解決辦法是由模塊作者顯式地提供包的索引。引入*的import語句遵循如下規(guī)定:如果包的“__init__.py”文件定義了一個名為“__all__”的列表,這個列表就作為從包內(nèi)導(dǎo)入*時要導(dǎo)入的所有模塊的名字表。因此當(dāng)包的新版本發(fā)布時需要包的作者確保這個列表是最新的。包的作者如果認(rèn)為不需要導(dǎo)入*的話也可以不支持這種用法。例如,文件Sounds/Effects/__init__.py 可以包含如下代碼: __all__ = ["echo", "surround", "reverse"] 這意味著from Sound.Effects import *將從Sound包中導(dǎo)入指定的三個子包。 如果沒有定義__all__,則from Sound.Effects import *語句不會導(dǎo)入Sound.Effects包中的所有子模塊;此語句只能保證Sound.Effects被導(dǎo)入(可能是執(zhí)行其初始化代碼“__init__.py ”)并導(dǎo)入包中直接定義的名字。這包括由“__init__.py”定義的任何名字和顯式導(dǎo)入的子模塊名。這也包括模塊中已經(jīng)在前面用import顯式地導(dǎo)入的子模塊,例如: import Sound.Effects.echo import Sound.Effects.surround from Sound.Effects import * 在這個例子中,echo和surround模塊被導(dǎo)入當(dāng)前名字空間,因為它們在執(zhí)行from...import 語句時已定義(在定義了__all__的情況下這一點也是成立的)。 注意用戶應(yīng)盡量避免使用從模塊或包中導(dǎo)入*的做法,因為這樣經(jīng)常導(dǎo)致可讀性差的代碼。盡管如此,在交互運行時可以用導(dǎo)入*的辦法節(jié)省敲鍵次數(shù),而且有些模塊在設(shè)計時就考慮到了這個問題,它們只輸出遵循某種約定的名字。注意,from 包 import 特定子模塊的用法并沒有錯,實際上這還是我們推薦的用法,除非程序還需要用到來自其它包的同名的子模塊。 6.4.2 包內(nèi)部引用 子模塊常常需要彼此引用。例如,模塊surround可能要用到模塊echo。事實上,這樣的引用十分常見,所以import語句首先從子模塊的所在包中尋找要導(dǎo)入的子模塊才在標(biāo)準(zhǔn)模塊搜索路徑查找。所以,模塊surround只要寫import echo或from echo import echofilter。如果在包含本模塊的包中沒有找到要導(dǎo)入的模塊,import語句將去尋找指定名字的頂級模塊。 當(dāng)包組織成子包時(比如例中的Sound包),沒有一種簡單的辦法可以引用兄弟包中的子模塊――必須使用子模塊的全名。例如,如果模塊Sound.Filters.vocoder要引用Sound.Effects 包中的echo模塊,它可以用Sound.Effects import echo。 第七章 輸入輸出 有幾種辦法可以從程序輸出;數(shù)據(jù)可以用可讀的形式顯示,或保存到文件中以備日后使用。本章討論一些輸入輸出的辦法。 7.1 輸出格式控制 到現(xiàn)在為止我們已經(jīng)看到了兩種輸出值的方法:表達式語句和print語句。(第三種方法是使用文件對象的write()方法,標(biāo)準(zhǔn)輸出文件可以用sys.stdout引用。參見庫參考手冊)。 我們常常需要控制輸出格式,而不僅僅是顯示空格分開的值。有兩種辦法控制輸出格式:一種辦法是自己進行字符串處理,用字符串的片斷和合并操作可以產(chǎn)生任何可以想象的格式。標(biāo)準(zhǔn)模塊string包含了諸如把字符串填充到指定的列寬這樣的有用操作,后面會有提及。 另一種辦法是使用%運算符,此運算符以一個字符串為左運算元,它按C的sprintf()函數(shù)格式把右運算元轉(zhuǎn)換為字符串,返回轉(zhuǎn)換結(jié)果。 問題是:如何把值轉(zhuǎn)換為字符串? 幸運的是,Python有一種辦法可以把任何值轉(zhuǎn)換為字符串:使用repr()函數(shù),或把值寫在兩個反向引號(``)之間。例如: >>> x = 10 * 3.14 >>> y = 200*200 >>> s = 'The value of x is ' + `x` + ', and y is ' + `y` + '...' >>> print s The value of x is 31.4, and y is 40000... >>> # 反向引號也適用于非數(shù)值型 ... p = [x, y] >>> ps = repr(p) >>> ps '[31.4, 40000]' >>> # 轉(zhuǎn)換字符串對字符串加字符串引號和反斜杠 ... hello = 'hello, world\n' >>> hellos = `hello` >>> print hellos 'hello, world\012' >>> # 反向引號內(nèi)可以是一個序表 ... `x, y, ('spam', 'eggs')` "(31.4, 40000, ('spam', 'eggs'))" 下面是兩種寫出平方、立方表的方法: >>> import string >>> for x in range(1, 11): ... print string.rjust(`x`, 2), string.rjust(`x*x`, 3), ... # 前一行的結(jié)尾逗號表示不換行 ... print string.rjust(`x*x*x`, 4) ... 1 1 1 2 4 8 3 9 27 4 16 64 5 25 125 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000 >>> for x in range(1,11): ... print'%2d %3d %4d' % (x, x*x, x*x*x) ... 1 1 1 2 4 8 3 9 27 4 16 64 5 25 125 6 36 216 7 49 343 8 64 512 9 81 729 10 100 1000 注意print輸出的各項之間額外加了一個空格,這是print的規(guī)定。 此例顯示了函數(shù)string.rjust()的用法,此函數(shù)可以把一個字符串放進指定寬度右對齊,左邊用空格填充。類似函數(shù)還有string.ljust()和string.center()。這些函數(shù)不向外輸出,只是返回轉(zhuǎn)換后的字符串。如果輸入字符串太長也不會被截斷而是被原樣返回。這樣的處理可能會使你的列對齊失效,但這可能比截斷要好一些,截斷的結(jié)果是我們看到一個錯誤的值。(如果你確實需要截斷的話總可以再加一層片斷,如string.ljust(x,n)[0:n])。 還有一個函數(shù)string.zfill(),可以在數(shù)值左邊填零。此函數(shù)可以處理帶有加減號的情況: >>> string.zfill('12', 5) '00012' >>> string.zfill('-3.14', 7) '-003.14' >>> string.zfill('3.14159265359', 5) '3.14159265359' %操作符的用法如下例: >>> import math >>> print 'The value of PI is approximately %5.3f.' % math.pi The value of PI is approximately 3.142. 如果有多個值可以用一個序表給出,這時格式字符串中要有多個格式,如: >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678} >>> for name, phone in table.items(): ... print'%-10s ==> %10d' % (name, phone) ... Jack ==> 4098 Dcab ==> 8637678 Sjoerd ==> 4127 大多數(shù)格式與C用法相同,要求要輸出的值的類型符合格式的需要。但是,如果你沒有引發(fā)例外錯誤的話也不會產(chǎn)生內(nèi)核堆列。Python的%s格式要寬得多:如果相應(yīng)的輸出項不是字符串對象,就先用str()內(nèi)置函數(shù)把它變成字符串。在格式指定中寬度指定為*號表示后面的輸出項中多出一個指定寬度的整數(shù)。C格式%n和%p未被支持。 如果你有一個長格式串不想把它分開,可以在指定格式的地方指定名字,這樣就不需要按次序去把格式和名字對應(yīng)起來,這種格式為“%(變量名)格式”,例如: >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678} >>> print 'Jack: %(Jack)d; Sjoerd: %(Sjoerd)d; Dcab: %(Dcab)d' % table Jack: 4098; Sjoerd: 4127; Dcab: 8637678 這里輸出項總是一個字典,字典的各項值是要輸出的值,字典的鍵值是各值的名字。這種輸出格式經(jīng)常與內(nèi)置函數(shù)var()配合使用,var()返回包含所有局部變量的字典。 7.2 讀寫文件 open()打開一個文件對象,經(jīng)常使用兩個參數(shù):“open(文件名,模式)”。例如: >>> f=open('/tmp/workfile', 'w') >>> print f <open file '/tmp/workfile', mode 'w' at 80a0960> 第一自變量是一個包含了文件名的字符串,第二自變量是文件打開方式的字符串。模式‘r ’表示讀取,‘w’表示只寫(已有的同名文件被清除),‘a(chǎn)’表示打開文件在尾部添加, ‘r+’表示打開文件既可以讀也可以寫。打開方式參數(shù)可選,缺省為‘r’模式。 在Windows和Macintosh中在模式中加入‘b’表示以二進制格式打開文件,如‘rb’、‘wb ’、‘r+b’。Windows對文本文件和二進制文件有不同的處理,文本文件中的換行字符在讀寫時有變化。這種對文件數(shù)據(jù)的幕后的修改不影響ASCII文本文件,但是會破壞二進制數(shù)據(jù)如JPEG 或“.EXE”文件的數(shù)據(jù)。讀寫這樣的文件一定要使用二進制格式。(Macintosh中文本模式的精確描述依賴于使用的C庫)。 7.2.1 文件對象的方法 本節(jié)后面的例子假設(shè)已經(jīng)建立了一個名為f的文件對象。 為了讀取文件內(nèi)容,調(diào)用f.read(size),可以讀入一定字節(jié)數(shù)的數(shù)據(jù)返回為一個字符串。size 是一個可選數(shù)值參數(shù),省略size或size取負(fù)值時讀入整個文件并返回為一個字符串;如果文件比你的機器內(nèi)存大一倍,那是你的問題。指定了正的size的時候之多讀入并返回size字節(jié)。如果讀到了文件尾,f.read()返回一個空串("")。如: >>> f.read() 'This is the entire file.\012' >>> f.read() '' f.readline()從文件中讀入一行,返回的字符串中將包括結(jié)尾的一個換行符(\n),如果文件的最后一行沒有換行符則由該行讀入的字符串也沒有結(jié)尾的換行符。這樣,由readline() 返回的結(jié)果不會有歧義,讀入結(jié)果為空串時一定是到了文件尾,讀入一個'\n'時為空行。 >>> f.readline() 'This is the first line of the file.\012' >>> f.readline() 'Second line of the file\012' >>> f.readline() '' f.readlines()反復(fù)調(diào)用f.readline(),返回一個包含文件所有行的列表。 >>> f.readlines() ['This is the first line of the file.\012', 'Second line of the file\012']f.write(string)把string的內(nèi)容寫入到文件中,返回None。 >>> f.write('This is a test\n') f.tell()返回文件對象的當(dāng)前讀寫為止,按從文件開始的字節(jié)數(shù)算。為了改變讀寫位置,使用“f.seek(位移,從哪里)”。讀寫位置按一個參考點加上位移來計算,參考點用“從那里”參數(shù)指定,取0時從文件頭開始算,取1時按當(dāng)前位置算,取2時從文件尾算。缺省值是0 ,從文件開始算。 >>> f=open('/tmp/workfile', 'r+') >>> f.write('0123456789abcdef') >>> f.seek(5) # 從文件頭前進5個字節(jié),到達第6個字符 >>> f.read(1) '5' >>> f.seek(-3, 2) # 轉(zhuǎn)到結(jié)尾前3個字符 >>> f.read(1) 'd' 用外一個文件后調(diào)用f.close()關(guān)閉文件,釋放打開文件所占用的系統(tǒng)資源。文件關(guān)閉后再使用此文件對象就無效了。 >>> f.close() >>> f.read() Traceback (innermost last): File "<stdin>", line 1, in ? ValueError: I/O operation on closed file 文件對象還有其它一些不太常用的方法,例如isatty()和truncate(),參見庫參考手冊。 7.2.2 pickle模塊 字符串可以很容易地從文件讀入或向文件寫出。讀入數(shù)值要麻煩一些,因為read()方法總是返回字符串,要把讀入的字符串傳給象string.atoi()這樣的函數(shù),把象‘123’這樣的字符串轉(zhuǎn)換為對應(yīng)的整數(shù)值123。但是,當(dāng)你想保存更復(fù)雜的數(shù)據(jù)類型如列表、字典或類實例時,讀寫就要復(fù)雜得多。 Python的設(shè)計使程序員可以不必反復(fù)編寫調(diào)試保存復(fù)雜數(shù)據(jù)類型的代碼,它提供了一個叫做pickle的標(biāo)準(zhǔn)模塊。這個令人驚異的模塊可以把幾乎任何Python對象轉(zhuǎn)換為字符串表示,這個過程叫做腌制,從對象的字符串表示恢復(fù)對象叫做恢復(fù)。在腌制和反腌制之間,對象的字符串表示可以保存在文件或數(shù)據(jù)中,甚至于通過網(wǎng)絡(luò)連接傳送到遠(yuǎn)程計算機上。 如果你有一個對象x,有一個可寫的文件對象f,最簡單的腌制對象的辦法是下面一行代碼: pickle.dump(x, f) 為了恢復(fù)對象,如果剛才的文件已打開用于讀取,文件對象名仍為f,則: x = pickle.load(f) (腌制和恢復(fù)還有其它用法,可以腌制多個對象,可以不把數(shù)據(jù)寫入文件,詳見庫參考手冊)。 pickle是保存Python對象并被其它程序或同一程序以后再運行時調(diào)用的標(biāo)準(zhǔn)辦法,這種做法的專用術(shù)語叫做“持久對象”。因為pickle使用廣泛,許多Python擴展模塊的作者都留意使新增加的數(shù)據(jù)類型如矩陣可以正確地腌制和恢復(fù)。 第八章 錯誤與例外 到現(xiàn)在為止我們只是提到了錯誤信息而沒有詳細(xì)討論,如果你運行了前面的例子可能已經(jīng)看到了一些錯誤信息。至少有兩種不同錯誤:句法錯和例外錯(exceptions)。 8.1 句法錯 句法錯也稱為語法分析錯,是你在學(xué)習(xí)Python的時候最可能犯的錯誤。 >>> while 1 print 'Hello world' File "<stdin>", line 1 while 1 print 'Hello world' ^ SyntaxError: invalid syntax 語法分析器重復(fù)出錯行,并用一個小‘箭頭’指向行內(nèi)最早發(fā)現(xiàn)錯誤的位置。錯誤是由箭頭前面的記號引起的(至少是在這里檢測到的)。在本例中,錯誤在關(guān)鍵字print處檢測到,因為它前面應(yīng)該有一個冒號(“:”)。錯誤信息中顯示了文件名和行號這樣如果錯誤發(fā)生在一個腳本文件中你就知道到哪里去找。 8.2 例外 即使語句或表達式句法沒有問題,在試圖運行的時候也可能發(fā)生錯誤。運行時檢測到的錯誤叫做例外,這種錯誤不一定必然是致命的:你很快就會學(xué)到如何在Python程序中處理例外。然而,多數(shù)例外不能被程序處理,這是會產(chǎn)生錯誤信息,如: >>> 10 * (1/0) Traceback (innermost last): File "<stdin>", line 1 ZeroDivisionError: integer division or modulo >>> 4 + spam*3 Traceback (innermost last): File "<stdin>", line 1 NameError: spam >>> '2' + 2 Traceback (innermost last): File "<stdin>", line 1 TypeError: illegal argument type for built-in operation 錯誤信息的最后一行顯示發(fā)生的情況。例外有不同的類型,類型作為錯誤信息的一部分顯示:上例中錯誤的類型有ZeroDivisionError、NameError和TypeError。作為例外類型顯示的字符串是發(fā)生的例外的內(nèi)置名。這對于所有內(nèi)置例外成立,但對用戶自定義例外不一定成立(用戶最好能遵守這樣的約定)。標(biāo)準(zhǔn)例外名是內(nèi)置的標(biāo)識符(不是保留關(guān)鍵字)。 此行的其余部分是錯誤的細(xì)節(jié),其解釋依賴于例外類型。錯誤信息前面的部分以堆棧反跟蹤的形式顯示了發(fā)生錯誤的上下文環(huán)境。一般這包含了列出源代碼行的一個列出源程序行的堆棧反跟蹤;然而,它不會顯示從標(biāo)準(zhǔn)輸入讀進的行。 庫參考手冊列出了內(nèi)置例外和其含義。 8.3 例外處理 可以編程序來處理選定的例外。請看下面的例子,顯示一些浮點數(shù)的倒數(shù): >>> numbers = [0.3333, 2.5, 0, 10] >>> for x in numbers: ... print x, ... try: ... print 1.0 / x ... except ZeroDivisionError: ... print '*** has no inverse ***' ... 0.3333 3.00030003 2.5 0.4 0 *** has no inverse *** 10 0.1 try語句是這樣工作的: 首先,運行try子句(在try和except之間的語句)。 如果沒有發(fā)生例外,跳過except子句,try語句運行完畢。 如果在try子句中發(fā)生了例外錯誤而且例外錯誤匹配except后指定的例外名,則跳過try子句剩下的部分,執(zhí)行except子句,然后繼續(xù)執(zhí)行try語句后面的程序。 如果在try子句中發(fā)生了例外錯誤但是例外錯誤不匹配except后指定的例外名,則此例外被傳給外層的try語句。如果沒有找到匹配的處理程序則此例外稱作是未處理例外,程序停止運行,顯示錯誤信息。 try語句可以有多個except子句,為不同的例外指定不同處理。至多只執(zhí)行一個錯誤處理程序。錯誤處理程序只處理相應(yīng)的try子句中發(fā)生的例外,如果同try語句中其它的錯誤處理程序中發(fā)生例外錯誤處理程序不會反應(yīng)。一個except子句可以列出多個例外,寫在括號里用逗號分開,例如: ... except (RuntimeError, TypeError, NameError): ... pass 最后一個except子句可以省略例外名,作為一個通配項。這種方法要謹(jǐn)慎使用,因為這可能會導(dǎo)致程序?qū)嶋H已出錯卻發(fā)現(xiàn)不了。 try ... except語句有一個可選的else子句,如有的話要放在所有except子句之后。else 的意思是沒有發(fā)生例外,我們可以把try子句中沒有發(fā)生例外時要做的事情放在這個子句里。例如: for arg in sys.argv[1:]: try: f = open(arg, 'r') except IOError: print '不能打開', arg else: print arg, '有', len(f.readlines()), '行' f.close() 例外發(fā)生時可能伴有一個值,叫做例外的參數(shù)。參數(shù)是否存在及其類型依賴于例外的類型。對于有參數(shù)的例外,except在自居可以在例外名(或表)后指定一個變量用來接受例外的參數(shù)值,如: >>> try: ... spam() ... except NameError, x: ... print 'name', x, 'undefined' ... name spam undefined 有參數(shù)的例外未處理時會在錯誤信息的最后細(xì)節(jié)部分列出其參數(shù)值。 例外處理程序不僅處理直接產(chǎn)生于try子句中的例外,也可以處理try子句中調(diào)用的函數(shù)(甚至是間接調(diào)用的函數(shù))中的例外。如: >>> def this_fails(): ... x = 1/0 ... >>> try: ... this_fails() ... except ZeroDivisionError, detail: ... print 'Handling run-time error:', detail ... Handling run-time error: integer division or modulo 8.4 產(chǎn)生例外 raise語句允許程序員強行產(chǎn)生指定的例外。例如: >>> raise NameError, 'HiThere' Traceback (innermost last): File "<stdin>", line 1 NameError: HiThere raise語句的第一個參數(shù)指定要產(chǎn)生的例外的名字。可選的第二參數(shù)指定例外的參數(shù)。 8.5 用戶自定義例外 程序中可以定義自己的例外,只要把一個字符串賦給一個變量即可。例如: >>> my_exc = 'my_exc' >>> try: ... raise my_exc, 2*2 ... except my_exc, val: ... print 'My exception occurred, value:', val ... My exception occurred, value: 4 >>> raise my_exc, 1 Traceback (innermost last): File "<stdin>", line 1 my_exc: 1 許多標(biāo)準(zhǔn)模塊用這種方法報告自己定義的函數(shù)中發(fā)生的錯誤。 8.6 定義清理動作 try語句還有另一個finally可選子句,可以用來規(guī)定不論出錯與否都要執(zhí)行的動作。 例如: >>> try: ... raise KeyboardInterrupt ... finally: ... print 'Goodbye, world!' ... Goodbye, world! Traceback (innermost last): File "<stdin>", line 2 KeyboardInterrupt finally子句不論try子句中是否發(fā)生例外都會執(zhí)行。例外發(fā)生時,先執(zhí)行finally子句然后重新提出該例外。當(dāng)try語句用break或return語句退出時也將執(zhí)行finally子句。 要注意的是,try語句有了except子句就不能有finally子句,有了finally子句就不能有except 子句,不能同時使用except子句和finally子句。需要的話可以嵌套。 第九章 類 Python是一個真正面向?qū)ο蟮恼Z言,它只增加了很少的新語法就實現(xiàn)了類。它的類機制是C++ 和Modula-3的類機制的混合。Python的類并不嚴(yán)格限制用戶對定義的修改,它依賴于用戶自覺不去修改定義。然而Python對類最重要的功能都保持了完全的威力。類繼承機制允許多個基類的繼承,導(dǎo)出類可以重載基類的任何方法,方法可以調(diào)用基類的同名方法。對象可以包含任意多的私有數(shù)據(jù)。 用C++術(shù)語說,所有類成員(包括數(shù)據(jù)成員)是公用的,所有成員函數(shù)是虛擬(virtual)的。沒有特別的構(gòu)建函數(shù)或銷毀函數(shù)(destructor)。如同在Modula-3中一樣,從對象的方法中要引用對象成員沒有簡捷的辦法:方法函數(shù)的必須以對象作為第一個參數(shù),而在調(diào)用時則自動提供。象在Smalltalk中一樣,類本身也是對象,實際上這里對象的含義比較寬:在Python 中所有的數(shù)據(jù)類型都是對象。象在C++或Modula-3中一樣,內(nèi)置類型不能作為基類由用戶進行擴展。并且,象C++但不象Modula-3,多數(shù)有特殊語法的內(nèi)置函數(shù)(如算術(shù)算符、下標(biāo)等)可以作為類成員重定義。 9.1 關(guān)于術(shù)語 Python的對象概念比較廣泛,對象不一定非得是類的實例,因為如同C++和Modula-3而不同于Smalltalk,Python的數(shù)據(jù)類型不都是類,比如基本內(nèi)置類型整數(shù)、列表等不是類,甚至較古怪的類型如文件也不是類。然而,Python所有的數(shù)據(jù)類型都或多或少地帶有一些類似對象的語法。對象是有單獨身份的,同一對象可以有多個名字與其聯(lián)系,這在其他語言中叫做別名。這樣做的好處乍一看并不明顯,而且對于非可變類型(數(shù)字、字符串、序表(tuple))等沒有什么差別。但是別名句法對于包含可變對象如列表、字典及涉及程序外部物件如文件、窗口的程序有影響,這可以有利于程序編制,因為別名有些類似指針:比如,傳遞一個對象變得容易,因為這只是傳遞了一個指針;如果一個函數(shù)修改了作為參數(shù)傳遞來的對象,修改結(jié)果可以傳遞回調(diào)用處。這樣就不必象Pascal那樣使用兩種參數(shù)傳遞機制。 9.2 Python作用域與名字空間 在引入類之前,我們必須講一講Python的作用域規(guī)則。類定義很好地利用了名字空間,需要了解Python如何處理作用域和名字空間才能充分理解類的使用。另外,作用域規(guī)則也是一個高級Python程序員必須掌握的知識。 先給出一些定義。 名字空間是從名字到對象的映射。多數(shù)名字空間目前是用Python字典類型實現(xiàn)的,不過這一點一般是注意不到的,而且將來可能會改變。下面是名字空間的一些實例:Python中內(nèi)置的名字(如abs()等函數(shù),以及內(nèi)置的例外名);模塊中的全局名;函數(shù)調(diào)用中的局部變量名。在某種意義上一個對象的所有屬性也構(gòu)成了一個名字空間。關(guān)于名字空間最重要的事要知道不同名字空間的名字沒有任何聯(lián)系;例如,兩個不同模塊可能都定義了一個叫“maximize ”的函數(shù)而不會引起混亂,因為模塊的用戶必須在函數(shù)名之前加上模塊名作為修飾。 另外,在Python中可以把任何一個在句點之后的名字稱為屬性,例如,在表達式z.real中,real是一個對象z的屬性。嚴(yán)格地說,對模塊中的名字的引用是屬性引用:在表達式modname.funcname 中,modname是一個模塊對象,funcname是它的一個屬性。在這種情況下在模塊屬性與模塊定義的全局名字之間存在一個直接的映射:它們使用相同的名字空間! 屬性可以是只讀的也可以是可寫的。在屬性可寫的時候,可以對屬性賦值。模塊屬性是可寫的:你可以寫“modname.the_answer = 42”。可寫屬性也可以用del語句閃出,如“del modname.the_answer”。 名字空間與不同時刻創(chuàng)建,有不同的生存周期。包含Python內(nèi)置名字的名字空間當(dāng)Python 解釋程序開始時被創(chuàng)建,而且不會被刪除。模塊的全局名字空間當(dāng)模塊定義被讀入時創(chuàng)建,一般情況下模塊名字空間也一直存在到解釋程序退出。由解釋程序的最頂層調(diào)用執(zhí)行的語句,不論是從一個腳本文件讀入的還是交互輸入的,都屬于一個叫做__main__的模塊,所以也存在于自己的全局名字空間之中。(內(nèi)置名字實際上也存在于一個模塊中,這個模塊叫做__builtin__ )。 函數(shù)的局部名字空間當(dāng)函數(shù)被調(diào)用時創(chuàng)建,當(dāng)函數(shù)返回或者產(chǎn)生了一個不能在函數(shù)內(nèi)部處理的例外時被刪除。(實際上,說是忘記了這個名字空間更符合實際發(fā)生的情況。)當(dāng)然,遞歸調(diào)用在每次遞歸中有自己的局部名字空間。 一個作用域是Python程序中的一個文本區(qū)域,其中某個名字空間可以直接訪問。“直接訪問” 這里指的是使用不加修飾的名字就直接找到名字空間中的對象。 雖然作用域是靜態(tài)定義的,在使用時作用域是動態(tài)的。在任何運行時刻,總是恰好有三個作用域在使用中(即恰好有三個名字空間是直接可訪問的):最內(nèi)層的作用域,最先被搜索,包含局部名字;中層的作用域,其次被搜索,包含當(dāng)前模塊的全局名字;最外層的作用域最后被搜索,包含內(nèi)置名字。 一般情況下,局部作用域引用當(dāng)前函數(shù)的局部名字,其中局部是源程序文本意義上來看的。在函數(shù)外部,局部作用域與全局作用域使用相同的名字空間:模塊的名字空間。類定義在局部作用域中又增加了另一個名字空間。 一定要注意作用域是按照源程序中的文本位置確定的:模塊中定義的函數(shù)的全局作用域是模塊的名字空間,不管這個函數(shù)是從哪里調(diào)用或者以什么名字調(diào)用的。另一方面,對名字的搜索卻是在程序運行中動態(tài)進行的,不過,Python語言的定義也在演變,將來可能發(fā)展到靜態(tài)名字解析,在“編譯”時,所以不要依賴于動態(tài)名字解析!(實際上,局部名字已經(jīng)是靜態(tài)確定的了)。 Python的一個特別之處是賦值總是進入最內(nèi)層作用域。關(guān)于刪除也是這樣:“del x”從局部作用域?qū)?yīng)的名字空間中刪除x的名字綁定(注意在Python中可以多個名字對應(yīng)一個對象,所以刪除一個名字只是刪除了這個名字與其對象間的聯(lián)系而不一定刪除這個對象。實際上,所有引入新名字的操作都使用局部作用域:特別的,import語句和函數(shù)定義把模塊名或函數(shù)名綁定入局部作用域。(可以使用global語句指明某些變量是屬于全局名字空間的)。 9.3 初識類 類引入了一些新語法,三種新對象類型,以及一些新的語義。 9.3.1 類定義語法 類定義的最簡單形式如下: class 類名: <語句-1> . . . <語句-N> 如同函數(shù)定義(def語句)一樣,類定義必須先執(zhí)行才能生效。(甚至可以把類定義放在if 語句的一個分支中或函數(shù)中)。在實際使用時,類定義中的語句通常是函數(shù)定義,其它語句也是允許的,有時是有用的――我們后面會再提到這一點。類內(nèi)的函數(shù)定義通常具有一種特別形式的自變量表,專用于方法的調(diào)用約定――這一點也會在后面詳細(xì)討論。 進入類定義后,產(chǎn)生了一個新的名字空間,被用作局部作用域――于是,所有對局部變量的賦值進入這個新名字空間。特別地,函數(shù)定義把函數(shù)名與新函數(shù)綁定在這個名字空間。 當(dāng)函數(shù)定義正常結(jié)束(從結(jié)尾退出)時,就生成了一個類對象。這基本上是將類定義生成的名字空間包裹而成的一個對象;我們在下一節(jié)會學(xué)到類對象的更多知識。原始的局部作用域(在進入類定義之前起作用的那個)被恢復(fù),類對象在這里被綁定到了類對象定義頭部所指定的名字。 9.3.2 類對象 類對象支持兩種操作:屬性引用和實例化。屬性引用的格式和Python中其它的屬性引用格式相同,即obj.name。有效的屬性名包括生成類對象時的類名字空間中所有的名字。 所以,如果象下面這樣定義類: class MyClass: "A simple example class" i = 12345 def f(x): return 'hello world' 則MyClass.i和MyClass.f都是有效的屬性引用,分別返回一個整數(shù)和一個函數(shù)對象。也可以對類屬性賦值,所以你可以對MyClass.i賦值而改變該屬性的值。 __doc__也是一個有效的屬性,它是只讀的,返回類的文檔字符串:“A simple example class”。 類實例化使用函數(shù)記號。只要把這個類對象看成是一個沒有自變量的函數(shù),返回一個類實例。例如(假設(shè)使用上面的類): x = MyClass() 可以生成該類的一個新實例并把實例對象賦給局部變量x。 9.3.3 實例對象 我們?nèi)绾问褂脤嵗龑ο竽兀款悓嵗欢脤傩砸眠@一種操作。有兩類有效的屬性。 第一類屬性叫做數(shù)據(jù)屬性。數(shù)據(jù)屬性相當(dāng)于Smalltalk中的“實例變量”,和C++中的“數(shù)據(jù)成員”。數(shù)據(jù)成員不需要聲明,也不需要在類定義中已經(jīng)存在,象局部變量一樣,只要一賦值它就產(chǎn)生了。例如,如果x是上面的MyClass類的一個實例,則下面的例子將顯示值16而不會留下任何痕跡: x.counter = 1 while x.counter < 10: x.counter = x.counter * 2 print x.counter del x.counter 類實例能理解的第二類屬性引用是方法。方法是“屬于”一個對象的函數(shù)。(在Python中,方法并不是只用于類實例的:其它對象類型也可以有方法,例如,列表對象也有append、insert 、remove、sort等方法。不過,在這里除非特別說明我們用方法來特指類實例對象的方法)。 類對象的有效方法名依賴于它的類。按照定義,類的所有類型為函數(shù)對象屬性定義了其實例的對應(yīng)方法。所以在我們的例子y,x.f是一個有效的方法引用,因為MyClass是一個函數(shù);x.i 不是方法引用,因為MyClass.i不是。但是x.f和MyClass.f不是同一個東西――x.f是一個方法對象而不是一個函數(shù)對象。 9.3.4 方法對象 方法一般是直接調(diào)用的,例如: x.f() 在我們的例子中,這將返回字符串‘hello world’。然而,也可以不直接調(diào)用方法:x.f 是一個方法對象,可以把它保存起來再調(diào)用。例如: xf = x.f while 1: print xf() 會不停地顯示“hello world”。 調(diào)用方法時到底發(fā)生了什么呢?你可能已經(jīng)注意到x.f()調(diào)用沒有自變量,而函數(shù)f在調(diào)用時有一個自變量。那個自變量是怎么回事?Python如果調(diào)用一個需要自變量的函數(shù)時忽略自變量肯定會產(chǎn)生例外錯誤――即使那個自變量不需要用到…… 實際上,你可以猜出答案:方法與函數(shù)的區(qū)別在于對象作為方法的第一個自變量自動傳遞給方法。在我們的例子中,調(diào)用x.f()等價于調(diào)用MyClass.f(x)。一般地,用n個自變量的去調(diào)用方法等價于把方法所屬對象插入到第一個自變量前面以后調(diào)用對應(yīng)函數(shù)。如果你還不理解方法是如何工作的,看一看方法的實現(xiàn)可能會有所幫助。在引用非數(shù)據(jù)屬性的實例屬性時,將搜索它的類。如果該屬性名是一個有效的函數(shù)對象,就生成一個方法對象,把實例對象(的指針)和函數(shù)對象包裝到一起:這就是方法對象。當(dāng)方法對象用一個自變量表調(diào)用時,它再被打開包裝,由實例對象和原自變量表組合起來形成新自變量表,用這個新自變量表調(diào)用函數(shù)。 9.4 一些說明 在名字相同時數(shù)據(jù)屬性會覆蓋方法屬性;為了避免偶然的名字沖突,這在大型程序中會造成難以查找的錯誤,最好按某種命名慣例來區(qū)分方法名和數(shù)據(jù)名,例如,所有方法名用大寫字母開頭,所有數(shù)據(jù)屬性名前用一個唯一的字符串開頭(或者只是一個下劃線),或方法名用動詞而數(shù)據(jù)名用名詞。 數(shù)據(jù)屬性可以被方法引用也可以被普通用戶(“客戶”)引用。換句話說,類不能用來構(gòu)造抽象數(shù)據(jù)類型。實際上,Python中沒有任何辦法可以強制進行數(shù)據(jù)隱藏——這些都是基于慣例。(另一方面,Python的實現(xiàn)是用C寫的,它可以完全隱藏實現(xiàn)細(xì)節(jié),必要時可以控制對象存取;用C寫的Python擴展模塊也有同樣特性)。 客戶要自己小心使用數(shù)據(jù)屬性——客戶可能會因為隨意更改類對象的數(shù)據(jù)屬性而破壞由類方法維護的類數(shù)據(jù)的一致性。注意客戶只要注意避免名字沖突可以任意為實例對象增加新數(shù)據(jù)屬性而不需影響到方法的有效性——這里,有效的命名慣例可以省去許多麻煩。 從方法內(nèi)要訪問本對象的數(shù)據(jù)屬性(或其它方法)沒有一個簡寫的辦法。我認(rèn)為這事實上增加了程序的可讀性:在方法定義中不會混淆局部變量和實例變量。 習(xí)慣上,方法的第一自變量叫做self。這只不過是一個習(xí)慣用法:名字self在Python中沒有任何特殊意義。但是,因為用戶都使用此慣例,所以違背此慣例可能使其它Python程序員不容易讀你的程序,可以想象某些類瀏覽程序會依賴于此慣例)。 作為類屬性的任何函數(shù)對象都為該類的實例定義一個方法。函數(shù)的定義不一定必須在類定義內(nèi)部:只要在類內(nèi)把一個函數(shù)對象賦給一個局部變量就可以了。例如: # Function defined outside the class def f1(self, x, y): return min(x, x+y) class C: f = f1 def g(self): return 'hello world' h = g 現(xiàn)在f、g和h都是類C的屬性且指向函數(shù)對象,所以它們都是C的實例的方法——其中h與g 完全等價。注意我們應(yīng)該避免這種用法以免誤導(dǎo)讀者。 方法可以用代表所屬對象的self自變量來引用本類其它的方法,如: class Bag: def empty(self): self.data = [] def add(self, x): self.data.append(x) def addtwice(self, x): self.add(x) self.add(x) 實例化操作(“調(diào)用”一個類對象)生成一個空對象。許多類要求生成具有已知初識狀態(tài)的類。為此,類可以定義一個特殊的叫做__init__()的方法,如: def __init__(self): self.empty() 一個類定義了__init__()方法以后,類實例化時就會自動為新生成的類實例調(diào)用調(diào)用__init__() 方法。所以在Bag例子中,可以用如下程序生成新的初始化的實例: x = Bag() 當(dāng)然,__init__()方法可以有自變量,這樣可以實現(xiàn)更大的靈活性。在這樣的情況下,類實例化時指定的自變量被傳遞給__init__()方法。例如: >>> class Complex: ... def __init__(self, realpart, imagpart): ... self.r = realpart ... self.i = imagpart ... >>> x = Complex(3.0,-4.5) >>> x.r, x.i (3.0, -4.5) 方法可以和普通函數(shù)一樣地引用全局名字。方法的全局作用域是包含類定義的模塊。(注意類本身并不被用作全局作用域!)雖然我們很少需要在方法中使用全局?jǐn)?shù)據(jù),全局作用域還是有許多合法的用途:例如,導(dǎo)入全局作用域的函數(shù)和模塊可以被方法使用,在同一模塊中定義的函數(shù)和方法也可以被方法使用。包含此方法的類一般也在此全局作用域中定義,下一節(jié)我們會看到一個方法為什么需要引用自己的類! 9.5 繼承 當(dāng)然,一個語言如果不支持繼承就談不到“類”。導(dǎo)出類的定義方法如下: class 導(dǎo)出類名(基類名): <語句-1> . . . <語句-N> 其中“基類名”必須在包含導(dǎo)出類定義的作用域中有定義。除了給出基類名外,還可以給出一個表達式,在基類定義于其它模塊中時這是有用的,如: class 導(dǎo)出類名 (模塊名.基類名): 導(dǎo)出類定義的運行和基類運行的方法是一樣的。生成類對象是,基類被記憶。這用于解決屬性引用:如果類中未找到要求的屬性就到基類中去查找。如果基類還有基類的話這個規(guī)則遞歸地應(yīng)用到更高的類。 導(dǎo)出類在實例化時沒有任何特殊規(guī)則。“導(dǎo)出類名()”產(chǎn)生該類的一個新實例。方法引用這樣解決:搜索相應(yīng)類屬性,如果必要的話逐級向基類查找,如果找到了一個函數(shù)對象就是有效的方法引用。 導(dǎo)出類可以重寫基類的方法。因為方法在調(diào)用同一對象的其它方法時并無任何特殊權(quán)限,如果基類中某一方法調(diào)用同一基類的另一方法,在導(dǎo)出類中該方法調(diào)用的就可能是已經(jīng)被導(dǎo)出類重寫后的方法了。(對C++程序員而言:Python中所有方法都是“虛擬函數(shù)”)。 導(dǎo)出類中重寫的方法可能是需要擴充基類的同名方法而不是完全代替原來的方法。導(dǎo)出類調(diào)用基類同名方法很簡單:“基類名.方法名(self, 自變量表)”。對類用戶這種做法偶爾也是有用的。(注意只有基類在同一全局作用域定義或?qū)霑r才能這樣用)。 8.5.1 多重繼承 Python也支持有限的多重繼承。有多個基類的類定義格式如下: class 導(dǎo)出類名 (基類1, 基類2, 基類3): <語句-1> . . . <語句-N> 關(guān)于多重繼承只需要解釋如何解決類屬性引用。類屬性引用是深度優(yōu)先,從左向右進行的。所以,如果在導(dǎo)出類定義中未找到某個屬性,就先在基類1中查找,然后(遞歸地)在基類1 的基類中查找,如果都沒有找到,就在基類2中查找,如此進行下去。 (對某些人來說寬度優(yōu)先——先在基類2和基類3中查找再到基類1的基類中查找——看起來更自然。然而,這需要你在確定基類1與基類2的屬性沖突時明確知道這個屬性是在基類1本身定義還是在其基類中定義。深度優(yōu)先規(guī)則不區(qū)分基類1的一個屬性到底是直接定義的還是繼承來的)。 很顯然,如果不加約束地使用多重繼承會造成程序維護的惡夢,因為Python避免名字沖突只靠習(xí)慣約定。多重繼承的一個眾所周知的問題是當(dāng)導(dǎo)出類有兩個基類恰好從同一個基類導(dǎo)出的。盡管很容易想到這種情況的后果(實例只有一份“實例變量”或數(shù)據(jù)屬性被共同的基類使用),但是這種做法有什么用處卻是不清楚的。 9.6 私有變量 Python對私有類成員有部分支持。任何象__spam這樣形式的標(biāo)識符(至少有兩個前導(dǎo)下劃線,至多有一個結(jié)尾下劃線)目前被替換成_classname__spam,其中classname是所屬類名去掉前導(dǎo)下劃線的結(jié)果。這種攪亂不管標(biāo)識符的語法位置,所以可以用來定義類私有的實例、變量、方法,以及全局變量,甚至于保存對于此類是私有的其它類的實例。如果攪亂的名字超過255個字符可能會發(fā)生截斷。在類外面或類名只有下劃線時不進行攪亂。 名字?jǐn)噥y的目的是給類一種定義“私有”實例變量和方法的簡單方法,不需擔(dān)心它的其它類會定義同名變量,也不怕類外的代碼弄亂實例的變量。注意攪亂規(guī)則主要是為了避免偶然的錯誤,如果你一定想做的話仍然可以訪問或修改私有變量。這甚至是有用的,比如調(diào)試程序要用到私有變量,這也是為什么這個漏洞沒有堵上的一個原因。(小錯誤:導(dǎo)出類和基類取相同的名字就可以使用基類的私有變量)。 注意傳遞給exec,eval()或evalfile()的代碼不會認(rèn)為調(diào)用它們的類的類名是當(dāng)前類,這與global語句的情況類似,global的作用局限于一起字節(jié)編譯的代碼。同樣的限制也適用于getattr() ,setattr()和delattr(),以及直接訪問__dict__的時候。 下面例子中的類實現(xiàn)了自己的__getattr__和__setattr__方法,把所有屬性保存在一個私有變量中,這在Python的新舊版本中都是可行的: class VirtualAttributes: __vdict = None __vdict_name = locals().keys()[0] def __init__(self): self.__dict__[self.__vdict_name] = {} def __getattr__(self, name): return self.__vdict[name] def __setattr__(self, name, value): self.__vdict[name] = value 9.7 補充 有時我們希望有一種類似Pascal的“record”或C的“struct”的類型,可以把幾個有名的數(shù)據(jù)項組合在一起。一個空類可以很好地滿足這個需要,如: class Employee: pass john = Employee() # 生成一個空職員記錄 # 填充記錄的各個域 john.name = 'John Doe' john.dept = 'computer lab' john.salary = 1000 一段需要以某種抽象數(shù)據(jù)類型作為輸入的Python程序經(jīng)常可以接受一個類作為輸入,該類只是模仿了應(yīng)輸入的數(shù)據(jù)類型的方法。例如,如果你有一個函數(shù)是用來格式化一個文件對象中的數(shù)據(jù),就可一個定義一個具有方法read()和readline()的類,該類可以不從文件輸入而是從一個字符串緩沖區(qū)輸入,把這個類作為自變量。 實例方法對象也有屬性:m.im_self是方法所屬的實例,m.im_func是方法對應(yīng)的函數(shù)對象。 9.7.1 例外可以是類 用戶自定義的例外除了可以是字符串對象以外還可以是類。這樣可以定義可擴充的分層的類例外結(jié)構(gòu)。 raise語句有兩種新的有效格式: raise 類, 實例 raise 實例 在第一種形式中,“實例”必須是“類”的實例或“類”的導(dǎo)出類的實例。第二種形式是 raise instance.__class__, instance 的簡寫。except語句除了可以列出字符串對象外也可以列出類。execpt子句中列出的類如果是發(fā)生的例外類或基類則是匹配的(反過來不對——except中如果是導(dǎo)出類而發(fā)生的例外屬于基類時是不匹配的)。例如,下面的程序會顯示B、C、D: class B: pass class C(B): pass class D(C): pass for c in [B, C, D]: try: raise c() except D: print "D" except C: print "C" except B: print "B" 注意如果把except子句的次序顛倒過來的話(“except B”放在最前),程序?qū)@示B,B ,B——因為第一個匹配的except子句被引發(fā)。 當(dāng)沒有處理的例外是類的時候,類名顯示在錯誤信息中,后面跟著一個冒號和一個空格,最后是實例用內(nèi)置函數(shù)str()轉(zhuǎn)換成字符串的結(jié)果。 第十章 進一步學(xué)習(xí) 希望通過這個入門課程的學(xué)習(xí)增強了你對使用Python的興趣。下一步怎么辦呢? 你可以進一步去讀《庫參考手冊》,該手冊對類型、函數(shù)、模塊等給出了完整的(盡管比較簡短)參考材料,可以節(jié)省你寫Python程序時的大量時間。標(biāo)準(zhǔn)Python軟件包括了大量的C 和Python的代碼,有讀取Unix信箱的,用HTTP接收文檔的,生成隨機數(shù)的,解析命令行選項的,寫CGI程序的,壓縮數(shù)據(jù)的,等等許多功能;粗略看一看庫參考手冊就可以知道有那些內(nèi)容。 Python的主網(wǎng)站是http://www.python.org,這里有程序代碼、文檔及有Python相關(guān)的其它網(wǎng)頁的信息。這個網(wǎng)站在世界上許多地方有鏡像,例如歐洲、日本、澳大利亞,訪問距離近的鏡像網(wǎng)站可能比訪問主網(wǎng)站快。還有一個非正式的網(wǎng)站是 http://starship.skyport.net ,包含了一系列有關(guān)Python的個人網(wǎng)頁,許多人在這里放了可下載的軟件。 對于關(guān)于Python的問題及錯誤報告,可以向comp.lang.python新聞組投稿,或向python-list@cwi.nl 郵件表發(fā)郵件。該新聞組和郵件表是互相轉(zhuǎn)發(fā)的,所以向其中一個發(fā)信會自動轉(zhuǎn)寄到另一個。每天有35-45份郵件,有問(答)問題的、建議新功能的、聲明新模塊的。在發(fā)信前應(yīng)該先查閱 http://www.python.org/doc/FAQ.html 處的常見問題表(FAQ),或在Python軟件的Misc/ 子目錄中尋找該文件。該FAQ回答了許多反復(fù)出現(xiàn)的問題,可能已經(jīng)有了你的問題的答案。 你可以通過加入Python軟件活動來支持Python團體,該團體負(fù)責(zé)python.org網(wǎng)站、ftp和電子郵件服務(wù)器,并組織Python研討班。關(guān)于如何加入?yún)⒁?http://www.python.org/psa/。 |
?