1 前言
Python是一個容易學習又功能強大的程序語言。它含有高效率的高階數據結構,也是一個簡單可是有效果的對象導向程序語言(object-oriented programming)。Python優雅的語法及動態型態識別(dynamic typing),加上直譯式(intepretion)的本質,使得它成爲一個在多種功能多種平臺上撰寫腳本(scripts)及快速開發的理想語言。
各類主要平臺的Python直譯器(interpreter)及延伸的標準連接庫(library)均可以在 Python的網站( http://www.python.org )上免費下載及自由流傳(包含原始碼及二元執行檔(binary form))。 在該網站上也有許多的檔案及連結,包括了免費、第三者開發的Python模塊、程序及工具, 以及許多的附帶文件。
Python的直譯器也能夠很容易的延伸,能夠加入新的由C或是C++(或其它能夠由C呼叫的程序語言)所寫的函數或是數據型態。Python也很適合用來看成其它應用程序的延伸語言(譯者言:好比說用Python來延伸CAD, DBMaker等的功能)。
本教學文件將非正式的介紹給讀者Python語言及系統的基本觀念及特性。若是讀者手邊有一個Python的直譯器將有助於得到實際的知識,可是本文件的解釋都很充足,因此若是單純離線閱讀也是能夠的。
若對於標準的對象及模塊有興趣的話,請參閱 Python Library Reference。 如果要知道正式Python語言的標準定義的話,可參考 Python Reference Manual 。 如有興趣用C或C++寫延伸的功能的話,請參考 Extending and Embedding the Python Interpreter 以及 Python/C API Reference。市面上也有許多更深刻探討Python的書籍。
本教學文件並不試圖完整的介紹每個Python的特性,甚至也不試圖介紹每個經常使用的功能。 相反的,本文件介紹許多Python值得認識的功能,而且讓讀者對這個語言的大體風貌有一個瞭解。 在讀完此書以後,讀者應該能夠開始讀及寫Python的模塊及程序,而且應該能夠開始學習各類在 Python Library Reference所介紹的模塊了。
2 開胃菜
若是你曾經寫過大型的shell script,你大概能瞭解那種感受:你想要新增長一個功能,可是這個script已經實在有夠大夠慢夠複雜的了,或者說,你想要加入的新功能須要呼叫系統功能或是其它函數,可是這些功能/函數只有C才能呼叫。你要解決的問題好像並無嚴重到要從新用C來寫整個程序,或者有些問題由於要用到可變長度的字符串或是特別的數據結構(像是用排序過的文件名稱組成序列(list)),用C來寫實在比shell麻煩的太多,又或者是你根本不是對C很熟。
另一個情境是這樣的:也許你要使用好幾個C的連接庫,可是標準開發C程序的過程(寫/編譯/測試/從新編譯)實在太花時間,你須要能快速的開發好軟件。又或者你已經些好一個應用程序,這個程序能夠使用一個延伸的語言來控制。你不想創造一種語言,而後還得寫好這個語言的編譯器,還得把這個編譯器跟你的程序放在一塊兒。
在這些狀況之下,Python也許正是你所須要的語言。Python雖然簡單,倒是徹徹底底的程序語言。對大型的程序來講,它比起shell能提供更多的結構性及支持。另一方面,它也提供了比C語言更多的錯誤檢查。因爲Python是一個很是高階的語言,因此它有許多內建的數據型態像是有彈性的數組及字典(dictionary)等等,若是用C來作的話得花上你大半天的時間。正是由於Python有較爲通常性的數據型態,Python能夠應用的範圍比起awk甚或是Perl要廣的不少,最起碼,Python跟這些語言同樣容易開發。
Python的另一個特色就是能夠將程序切成小模塊,而後這些模塊還能夠應用在其它的程序之中。Python自己也有一個至關大的標準模塊庫可讓你來使用,或者看成學習Python程序設計的範例。在Python中也有內建的模塊能夠提供許多功能,諸如:檔案I/O、系統呼叫、sockets,甚至是與Tk之類的GUI工具互動的接口。
Python是一個直譯式的語言,能夠省掉你在開發程序時很多編譯及連結程序的時間。這個Python的直譯器甚至能夠交互式的使用,讓你在寫一些小程序來試驗Python語言的特性,或是測試程序時能夠寫節省很多時間。你還能夠用Python直譯器來看成計算器呢。
Python讓你能夠寫出很是精練及可讀性高的程序。用Python寫出的程序一般比用C或C++寫的程序要短得多,其理由以下:
? 由於其高階的數據型態,使得你能夠用很簡單的敘述(statement)就可以表達複雜的運做過程
? Python使用縮排來代替C/C++中常見的先後括號{}
? Python不須要變量或是參數的宣告
Python 是延伸性高的語言。若是你知道如何寫C語言的程序的話,你很容易就能夠在Python的直譯器中加入新的內建函數(function)或是模塊,這樣作的好處是你可讓程序中關鍵的部分速度調到最快,或者是連結Python到binary的連接庫(例如是廠商作好的圖形連接庫)去。一但你真的須要,你也能夠把Python直譯器加入到你用C寫的應用程序裏面去,而後Python就變成你的應用程序的延伸或是商業化的語言了。
另一提的是,這個程序的命名由來是源自於BBC著名的節目``Monty Python's Flying Circus'',跟其它噁心的爬蟲類沒有任何關係。若是你的文件中要提到Monty Python的話,不但照準,並且還至關鼓勵。
2.1 而後呢
如今你應該對Python感到有一些興趣了吧,接下來你將看到比較多的的細節討論。學習語言的最好途徑是使用之,趕快動手吧。
在下一章咱們將討論到如何使用Python的直譯器,雖然至關的普通,可是若是你要嘗試一下以後的範例的話,這是重要的基礎。
本教學文件的其他部分將用許多的例子介紹Python語言的各類不一樣的特性,先從簡單的表示式(expressions)開始,將會談到敘述(statements)及數據型態,而後是函式(functions)及模塊(module),最後會談到較高深的觀念像是例外情形(exceptions)及使用者自訂的類別(user-defined classes)等等。
3 使用Python的直譯器
3.1 如何啓動直譯器
在Unix之類的操做系統上,若是有安裝的話,Python直譯器一般安裝在 /usr/local/bin/python 這個目錄中。你可能須要先在Shell中設定尋找 /usr/local/bin 目錄,這樣你才能夠在Shell中打入如下的指令來啓動Python直譯器
python
你的Python直譯器安裝的位置是能夠在安裝時設定的,所以也有可能安裝在其它的地方。你也許須要問你周遭的Python大師或是系統管理員才能知道正確的安裝位置( /usr/local/python 是另一個廣泛的可能安裝所在)。
要離開Python直譯器的話,打入EOF的字符( 在Unix上是 Control-D ,在DOS及Windows上是 Control-Z 就會使得直譯器離開(zero exit status)。若是行不通的話,你能夠打入如下指令離開直譯器: "import sys; sys.exit()".
Python直譯器使用每行編輯,應該不難使用。在Unix上,也許安裝Python直譯器的人有安裝使用GNU readline連接庫的功能,這樣的話你會有交互式編輯以及編輯過去數據的功能。最簡單的檢查你有沒有這項功能的方法就是在Python的提示之下打入Control-P ,若是有嗶聲的話,就表示你有這項功能,你能夠翻到 附錄 A 去看特殊鍵的用法。若是你沒有聽到嗶聲,或是隻出現 P 的話,就表示你沒有這項功能,你得使用退格鍵(backspace)來清除目前所在行的字符了。
Python直譯器的操做方法根Unix shell很像:當被呼叫時所連結的標準輸入是tty device(終端機)的話,直譯器會互動的讀及執行所輸入的指令。當被呼叫時加入文件名稱參數或所連結的標準輸入是連到檔案的話,直譯器就會讀入並執行該檔所含有的 script 。
第三種啓動直譯器的方法是打入如下的指令 "python -c command [arg] ..." ,這個指令會執行 command 所表明的敘述(這跟shell的 -c option很像),由於Python敘述(statement)常有空白及特殊字符,因此用此法時能夠把 command 所表明的敘述用」」括起來,以避免跟shell的其它特殊字符或是參數有所混淆。
要注意的是 "python file" 指令跟 "python <file"指令是有所區分的。對後者而言,不僅僅只有執行這個script,並且程序中有關輸入的需求(例如呼叫 input() 或是 raw_input() ) 也都會由這個 file 來知足。因爲此file已經在程序執行之初已經被從頭至尾讀過一次,因此一執行這個程序將會立刻就碰到了EOF。相反的對於前一個寫法來講,程序的輸入需求是由任何連結到Python直譯器的標準輸入(standard input)的裝置或檔案來知足的,而這個也許纔是你所想要的結果。
當script檔案在使用的時候,也許你會想要執行這個script而後還能夠以後繼續進入互動的模式。這時你能夠加入 -i 這個選項。可是如同前一段所說的,若是此script是由標準輸入來讀進去的話就沒有辦法這樣作了。
3.1.1 參數的傳遞
若是interpreter認識sys的話(譯:可用「import sys」指令),script的文件名及附加傳入的參數都會被紀錄在 sys.argv 這個變量並並傳給script來使用。sys.argv 是一列的字符串,長度至少爲1,若是你什麼都檔案或參數都沒傳的話, sys.argv[0] 就是一個空字符串。若是script的名字是 '-' (就是標準輸入的意思)的話, sys.argv[0] 就會被設爲 '-' 。當使用 -c command 的話, sys.argv[0] 就被設定爲 '-c' 全部的在 -c command 以後的option(例如 –i )都會被當成 sys.argv 而被command所處理,因此就不是看成option同樣的來看待了。
3.1.2 互動模式
當指令是由tty終端機來傳入時,咱們稱之爲互動模式( interactive mode)。在此模式之下會出現主要的命令提示符號( primary prompt)來提示輸入下一個指令,這個primary prompt一般是 "> >> " 。若是是指令是延續上一行的話就會出現secondary prompt符號,這個 secondary prompt 就一般是 "... " 。一進入python的互動模式的話直譯器會出現一個歡迎信息,以及版本編號輯版權說明,接下來就是第一個prompt。以下所示:
python
Python 1.5.2b2 (#1, Feb 28 1999, 00:02:06) [GCC 2.8.1] on sunos5
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>>
當你輸入須要多行的結構時,直譯器就會自動會出現延續上一行的prompt符號,下面的例子是 if 敘述的狀況:
>>> the_world_is_flat = 1
>>> if the_world_is_flat:
... print "Be careful not to fall off!"
...
Be careful not to fall off!
3.2 直譯器及其周邊環境
3.2.1 程序錯誤處理
當有錯誤產生時,直譯器就會在屏幕印出錯誤的信息以及stack trace的全部數據。在互動模式之下,印完數據以後會再印出prompt來。若是輸入是來自於檔案的話,在出現錯誤的狀況下直譯器在印出stack trace以後程序會以nonzero exit 的狀態結束。(此處討論不包含已經由 try 敘述及 except 子句處理外的情況(Exceptions))。有些的程序錯誤是沒有辦法挽救而且會形成nonzero exit的結束狀況,這經常是內部的不一致或是某種running out of memory所形成。全部的錯誤信息都會被寫入至標準error stream之中,正常的程序執行的輸出則會寫入到標準(standard output)輸出之中。
若是在primary或是secondary prompte之下打入中斷字符(一般是 Control-C 或是 DEL),這會形成輸入的中斷而且會回到prompt之下。 (有一個GNU Readline package的問題可能會使這個功能失效。) 在指令執行之中打入中斷字符則會引發 KeyboardInterrupt 的exception,而這是能夠在 try 敘述中處理的。
3.2.2 執行Python腳本
在BSD之類的Unix 系統上,咱們能夠在script的最前面加入如下的敘述(相似shell script),並改變檔案屬性爲可執行:
#! /usr/bin/env python
如此script就會變成可執行檔,能夠直接執行 (假設Python的直譯器是在user的 $PATH) 變量之中) 。 "#!" 這兩個字必須在script檔案的最前面。值得一提的是 "#" 在Python之中也看成註解(comment)部分開始的符號。
3.2.3 交互式啓動檔
當你使用互動模式的時候,若是能夠在每次直譯器要啓動時先執行一些命令的話將是頗有用的。要達成如此功能,你能夠設定一個文件名稱給環境變量 $PYTHONSTARTUP ,這個檔案能夠包含你想要在啓動時執行的命令,相似 .profile 在Unix shell中的用法。
這個啓動檔(startup file)只有對在互動模式下有效,若是你用Python讀入script時就沒有用,在當 /dev/tty 是命令的輸入來源也沒有用(其它的狀況與互動模式相相似)。這個startup file所執行命令的命名空間是與其它互動模式下輸入的指令相同,因此在startup file內定義或是import的對象,在以後的互動模式指令中都是能夠直接使用的。你也能夠在這個startup file中改變 sys.ps1 及 sys.ps2 ,如此就能夠改變你的primary prompt及secondary prompt。
若是你在你的startup file中想要使用另外的在目前目錄的startup file,你只須要在主要startup file (global start-up file)寫入 "if os.path.isfile('.pythonrc.py'): execfile('.pythonrc.py')" 。若是你想要在你的script之中使用startup file的話,你必須在你的script中寫入:
import os
filename = os.environ.get('PYTHONSTARTUP')
if filename and os.path.isfile(filename):
execfile(filename)
4 非正式的Python介紹
在底下的例子裏,你能夠很容易區別凡是須要輸入的地方都會出現prompts (">> > " 或 "... "),凡是輸出的結果則沒有。若是你想要跟着這個教學文件一塊兒作的話,你就得打入全部在prompts以後的指令,凡是沒有prompts出現的行就是直譯器輸出的結果。值得注意的是,secondary promt 以後若是什麼東西都沒有,表示這是一個空行(直接按ENTER的結果),也表示這是一個多行指令的結束。
在本文件中的大部分例子,都有加上註釋,甚至是那些互動模式下的例子。註釋(comment)在Python中是以 " #" 以後的東西都是註釋(譯:跟Perl同樣)。註釋能夠自成一行,也能夠跟在空格符或是程序代碼的後面。可是,若是 " #" 是在字符串常數(string literal)之中的話,就不表明註釋的意義,而只是一個普通字符罷了。
底下是一些例子:
# this is the first comment
SPAM = 1 # and this is the second comment
# ... and now a third!
STRING = "# This is not a comment."
4.1 把Python看成計算器來用
如今咱們來試一試一些簡單的Python指令吧。請先啓動Python的直譯器而且等待primary prompt( " >>> " )的出現。(應該不會好久的)
4.1.1 數字
直譯器就好像一個計算器同樣:你能夠打入一個表示式(expression),而後直譯器會把這個expression的執行結果秀出來。Expression的語法都很簡單直接,通常的運算符號 +, -, * 以及 / 的用法就跟其它的程序語言(像是Pascal或C)同樣。你也能夠用括號 "( )" 來表示運算執行的前後次序。例子以下:
>>> 2+2
4
>>> # This is a comment
... 2+24
>>> 2+2 # and a comment on the same line as code
4
>>> (50-5*6)/4
5
>>> # Integer division returns the floor:
... 7/3
2
>>> 7/-3
-3
跟C語言同樣,等於符號 ("=") 實際上是表示設定某個值給一個變量的意思。雖然設定 ("=") 運算自己是有結果值的,可是直譯器並不會輸出其結果來。
>>> width = 20
>>> height = 5*9
>>> width * height
900
一個值是能夠同時設給許多變量的:
>>> x = y = z = 0 # Zero x, y and z
>>> x
0
>>> y
0
>>> z
0
浮點數的運算在Python裏面也是支持的,若是整數與浮點數(帶小數點或e的數)進行運算的話,整數部分會先轉換(convert)成浮點數再進行運算。
>>> 4 * 2.5 / 3.3
3.0303030303
>>> 7.0 / 2
3.5
甚至連複數的運算也支持喔,只須要把虛數部分加上 "j" 或是 " J"在其後就能夠了。若是實部不爲零的話,複數的寫法就寫成 "(real+ imagj)" 。或者,咱們也能夠用函數的方式來表示複數爲 "complex(real , imag)" 的形式。
>>> 1j * 1J
(-1+0j)
>>> 1j * complex(0,1)
(-1+0j)
>>> 3+1j*3
(3+3j)
>>> (3+1j)*3
(9+3j)
>>> (1+2j)/(1+1j)
(1.5+0.5j)
複數的虛數部分及實數部分的值都是以浮點數(float point numbers)來表示的,若是 z 表明一個複數的話,你能夠很輕易的用 z.real 以及 z.imag 獲得一個複數的實數部分及虛數部分。
>>> a=1.5+0.5j
>>> a.real
1.5
>>> a.imag
0.5
複數沒有辦法直接用 (float(), int() 或是 long()) 轉換成浮點數或是整數。事實上,複數沒有直接對應的實數,你必須用 abs(z) 來獲得 z 的magnitude(以浮點數表示),或是如上所述用z.real 直接獲得其實數部分。
>>> a=1.5+0.5j
>>> float(a)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: can't convert complex to float; use e.g. abs(z)
>>> a.real
1.5
>>> abs(a)
1.58113883008
在互動模式之下,最後一個印出來的expression的值會儲存在一個特殊變量 "_ " 之中。這表示,當你用Python的直譯器來看成計算器用的時候,想要連續作運算實際上是方便許多的。以下例:
>>> tax = 17.5 / 100
>>> price = 3.50
>>> price * tax
0.61249999999999993
>>> price + _
4.1124999999999998
>>> round(_, 2)
4.1100000000000003
對於使用者來講, "_" 這個變數是一個只讀的變數。你沒有辦法設定一個值給它,當你這樣作的時候,事實上你是從新創造一個同名的變量,可是跟以前系統內建的 "_" 這個變量是一點關係也沒有的了。
4.1.2 字符串
除了數字以外, Python也有能力處理字符串(string)。字符串在Python中有不少種表達方式,它能夠放在雙括號」」之中,也能夠放在單括號’’裏面:
>>> '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.'
字符串常數(string literals)是能夠跨越多行的,其表示方法有不少。若是要換行的話能夠用」
}」符號來表示之。以下例:
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
這個例子會印出如下的結果:
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.
你也能夠用成對的三個單引號( """ ) 或雙引號 ( ''' ) 來表示字符串。在此狀況下你所打入的ENTER就會直接被解讀爲換行符號而不須要再用\n了。
print """
Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to
"""
這個例子會印出如下的結果:
Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to
若是你打入的expression是字符串的運算,運算的結果的一樣的會由直譯器顯示出來,並且顯示的方式跟你直接打入字符串常數(string literals)是同樣的:會在引號之中,全部有escape character 「\」表示的字符都會依樣的顯示出來。若是字符串自己包含有單引號,整個字符串就會用雙引號括起來,要否則就會只用單引號來把整個字符串括起來。(若是你使用 print 這個敘述(statement)來印出字符串的話,屏幕的輸出就不會有引號出現,並且字符串中的escape character (\」表示的特殊字符) 都會顯示出其所表明的意義來。)
字符串能夠用 + 這個操做數來相加 (鏈接起來),或是用 * 這個操做數來重複之。請看例子:
>>> word = 'Help' + 'A'
>>> word
'HelpA'
>>> '<' + word*5 + '>'
'<HelpAHelpAHelpAHelpAHelpA>'
若是你把兩個字符串常數放在一塊兒,它們自動就會相加起來。因此,上面的例子的第一行也能夠寫做 "word = 'Help' 'A'" 。不過這個方法只適用於兩個字符串常數的相加,其它狀況就不適合了。請看例子:
>>> import string
>>> 'str' 'ing' # <- This is ok'string'
>>> string.strip('str') + 'ing' # <- This is ok'string'
>>> string.strip('str') 'ing' # <- This is invalid
File "<stdin>", line 1
string.strip('str') 'ing'
^
SyntaxError: invalid syntax
如同在C語言同樣,字符串是有標記(subscript(index))的,第一個字符的標記(subscript(index))就是0。在Python中沒有另一個字符character數據型態,一個字符就是一個長度爲 1的字符串。就像是在Icon語言同樣,字符串是能夠用其subscript(index)來切出( slice notation )其中的一部份的,其語法爲 ""。
>>> word[4]
'A'
>>> word[0:2]
'He'
>>> word[2:4]
'lp'
與C不一樣的是,Python的字符串是不可改變的(immutable),若是你想要改變其中的一個字符或是一個部份(slice),你會獲得一個錯誤的信息:
>>> word[0] = 'x'
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: object doesn't support item assignment
>>> word[:-1] = 'Splat'
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: object doesn't support slice assignment
可是你能夠任意使用一個字符串的一個字符或是一個部份(slice)來創造出另外一個字符串,這是徹底可行的:
>>> 'x' + word[1:]
'xelpA'
>>> 'Splat' + word[-1:]
'SplatA'
當你用字符串切片(string slice)的語法時,能夠使用其預約(default)的subscript(index)值,這是很方便的。第一個subscript(index)的默認值是0,第二個subscript(index)的默認值則是這個字符串的總體長度。
>>> word[:2] # The first two characters
'He'
>>> word[2:] # All but the first two characters
'lpA'
因此, s[:i] + s[i:] 會剛好等於 s 。你能夠想想爲何:
>>> word[:2] + word[2:]
'HelpA'
>>> word[:3] + word[3:]
'HelpA'
若是你用一些奇怪的index來切割字符串,Python直譯器也都處理的很好:若是第二個index太大的話就自動代換爲字符串的長度,若是第二個index比第一個index還要小的話就自動傳回一個空字符串。
>>> word[1:100]
'elpA'
>>> word[10:]
''
>>> word[2:1]
''
字符串的index甚至能夠是負數,如果負數的話,就必須從字符串的尾巴開始算起。以下例:>>> word[-1] # The last character
'A'
>>> word[-2] # The last-but-one character
'p'
>>> word[-2:] # The last two characters
'pA'
>>> word[:-2] # All but the last two characters
'Hel'
可是 -0 事實上是等於 0 ,因此不會從尾巴開始算起。
>>> word[-0] # (since -0 equals 0)
'H'
若是負數index超過字符串的範圍的話,就自動只會到最大可能的範圍,可是若是不是切割一部份的話就會形成錯誤的情形:
>>> word[-100:]
'HelpA'
>>> word[-10] # error
Traceback (innermost last):
File "<stdin>", line 1
IndexError: string index out of range
最好避免錯誤的方法是把index當作是指向字符及字符間位置的指針,字符串的最開頭是0,字符串的結尾處就是字符串的長度。以下圖所示:
+---+---+---+---+---+
| H | e | l | p | A |
+---+---+---+---+---+
0 1 2 3 4 5
-5 -4 -3 -2 -1
上圖的數字部分第一行表明的是正數的index,由0到字符串的長度,第二行表明的是負數的index。字符串的切片(slice)很容易就能夠看出來,就是兩個index之間的全部字符組合成的字符串囉。
對於正數的index來講,若是兩個index都在範圍以內,字符串的切片(slice)的長度就正好是其兩個index相減的結果。舉例來講 word[1:3] 的長度就正好是 2。
Python內建的 len() 函式能夠幫助咱們獲得字符串的長度值。
>>> s = 'supercalifragilisticexpialidocious'
>>> len(s)
34
4.1.3 Unicode字符串
從Python 2.0 開始Python支持一種新的儲存文字數據的數據型態:Unicode物件(object)。使用這個對象你能夠儲存並控制Unicode的資料(詳見 http://www.unicode.org ) ,而且這個對象跟已經存在的字符串(string)對象是徹底能夠互相整合,而且在須要時能夠互相轉換的。
使用Unicode的好處是能夠處理各類不一樣國家語言的字符。在Unicode以前,在一個code page裏只有256個字符能夠使用在script中。這個限制的結果經常形成軟件國際化(internationalizetion,一般寫做 "i18n" -- "i" + 18個字符 + "n")時候的困擾。Unicode的出現定義一個全部script均可以用的code page,如此就解決了這個問題。
在Python中要創造一個Unicode字符串就跟創造一個普通字符串同樣容易:
>>> u'Hello World !'
u'Hello World !'
在引號以前小寫的 "u" 表明這個字符串是一個Unicode字符串。若是你要用到特殊字符,你可能要使用Python的Unicode特殊字符編碼( Unicode-Escape encoding)。底下的範例示範如何使用之:
>>> u'Hello\\u0020World !'
u'Hello World !'
上面的u0020 表示在這個位置要插入一個由十六位0x0020所表明的Unicode字符 (就是空格符啦)。
其它的字符也是同樣的會被解讀爲其對應的Unicode字符。因爲Unicode對應中的前256 個Unicode字符正好就是大部分歐美國家使用的Latin-1 編碼字符,因此其轉換是更加的容易。
對於專家們來講,有一個字符串的原始模式(raw mode)能夠使用。你必須再加上一個小寫 'r' 來使Python 使用這一個原始的Unicode特殊字符編碼( Raw-Unicode-Escape encoding)。只有當uXXXX 之中的小寫 'r' 有奇數的'\'時纔會用到這一個編碼的。
>>> ur'Hello\u0020World !'
u'Hello World !'>>> ur'Hello\\u0020World !'
u'Hello\\\\u0020World !'
這個原始模式(raw mode)一般用在當你的字符串裏面有一大堆的反斜線 '\' 時 ,例如regular expressions(正規表示)時就經常使用到。
除了這些標準的編碼以外, Python還提供了一整套的方法讓你能夠從以知的編碼中創造出Unicode字符串來。
Python內建的 unicode() p() 函式可讓你使用全部的已註冊的Unicode譯碼/編碼系統(codecs (COders and DECoders))。 這個 codes 能夠與大部分的系統互相轉換,包括 Latin-1, ASCII , UTF-8 以及 UTF-16 等等。上面所提到的最後兩種系統是可變長度的編碼系統,能夠來儲存8位及16位的Unicode字符。Python預設使用UTF-8爲預設編碼系統。當你印出Unicode或是將Unicode寫入檔案時都會使用到。
>>> u"??ü"
u'\344\366\374'
>>> str(u"??ü")
'\303\244\303\266\303\274'
若是你要使用一個特別的編碼系統,可是要印出對應的Unicode碼時,你能夠使用 unicode() 函式,加上這個編碼系統的名稱看成第二個參數。
>>> unicode('\303\244\303\266\303\274','UTF-8')
u'\344\366\374'
若是要把Unicode字符串轉換爲通常的字符串編碼時,能夠使用Unicode對象的 encode() 方法(method)。
>>> u"??ü".encode('UTF-8')
'\303\244\303\266\303\274'
4.1.4 列(List)
(譯:硬要翻譯list實在太不方便,我直接用原文囉)
Python可以瞭解一些較爲 複雜 的數據型態,這些數據型態大可能是用來處理一羣的其它數據值。最方便使用的要算是 list 了,一個list能夠寫成一串由逗號分開的值(東西),而後用角括號括起來便成。放在list裏的東西不須要是同一個數據型態
>>> a = ['spam', 'eggs', 100, 1234]
>>> a
['spam', 'eggs', 100, 1234]
跟字符串的index用法相同,list的index也由0開始,一樣你能夠用index來切割lists、組合兩個list等等:
>>> 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!']
與字符串不相同的是,字符串的個別字符是不可變更的( immutable ),可是list的個別成員是能夠自由改變的。
>>> a
['spam', 'eggs', 100, 1234]
>>> a[2] = a[2] + 23
>>> a
['spam', 'eggs', 123, 1234]
你也能夠設定一個值或是一個list給一個list的切割部分(slice),可是這樣的結果會改變整個list的長度:
>>> # Replace some items:
... a[0:2] = [1, 12]
>>> a
[1, 12, 123, 1234]
>>> # Remove some:
... a[0:2] = []
>>> a
[123, 1234]
>>> # Insert some:
... a[1:1] = ['bletch', 'xyzzy']
>>> a
[123, 'bletch', 'xyzzy', 1234]
>>> a[:0] = a # Insert (a copy of) itself at the beginning
>>> a
[123, 'bletch', 'xyzzy', 1234, 123, 'bletch', 'xyzzy', 1234]
內建的 len() 函式仍然可用在list上面:
>>> len(a)
8
一個list也能夠是另外一個list的成員(這叫做巢狀list, nested list),參考下例:
>>> q = [2, 3]
>>> p = [1, q, 4]
>>> len(p)
3
>>> p[1]
[2, 3]
>>> p[1][0]
2
>>> p[1].append('xtra') # See section 5.1
>>> p
[1, [2, 3, 'xtra'], 4]
>>> q
[2, 3, 'xtra']
注意前一個例子, p[1] 以及 q 事實上指得是同一個對象。咱們在以後還會再討論對象的語法( object semantics )。
4.2 邁向程序設計的第一步
固然Python能作比二加二更有用更復雜的事,例如說,咱們能夠寫一個程序來印出費氏數列( the Fibonacci series )來:
>>> # Fibonacci series:
... # the sum of two elements defines the next
... a, b = 0, 1
>>> while b < 10:
... print b
... a, b = b, a+b
...
1
1
2
3
5
8
這個範例告訴了咱們不少新的事情:
? 程序的第一行是一個多重設定( multiple assignment ):兩個變量 a 以及 b 同時都設定了新的值0 與 1。 程序的最後一行再次使用這個技巧,此次在設定符號(等號)的右邊咱們使用了expression,全部在右邊的expression會先求得其值(evaluate)而後才進行設定(assign)的動做。對於在右邊的expression來講,其evaluate的次序則是由左至右的。
? 在 while 循環中,只要條件符合(在這裏是 b < 10 ), 這個while 循環就會一直執行。與C相同的是,對Python而言只要是非零的整數都表明在決定true/false的狀況下都表明true,0則表明false。咱們也能夠在循環的條件的地方放入字符串或是一個list,只要這個字符串或list的長度不是零就表明true,如果空字符串或空的list就表明false。在這個例子裏,咱們比較兩個值的大小。比較的操做數與C是徹底相同的: < (小於), > (大於), == (等於), <= (小於或等於), >= (大於或等於) 以及 != (不等於)。
? 在循環中的執行部分是 縮排 的:縮排在Python中是表示一羣敘述的方法(way of grouping statements)。Python沒有(尚未)提供夠聰明的行排版機制,因此每一個要縮排的行你都得打入空格鍵或是tab鍵來縮排。實際的工做環境中,你也許會有本身的文字編輯器,大部分的編輯器會自動幫你作縮排的工做。當在互動模式下輸入一個複合的statement時(一個由許多statements組合成的statement),最後你須要再打入一個空白行(譯:按ENTER鍵)來告訴直譯器這個statement已經完成了(直譯器沒辦法猜你何時完成這個statement)。值得注意的是,若是你的statement是屬於同一羣(block)的話,你縮排的距離就要是同樣的。
? print 這個敘述會印出一個expression的結果值,這點與咱們以前所作的僅僅打入expression是不一樣的。不一樣之處在於對字符串及多個的expression來講,用 print 不會印出字符串的引號,也會在多個expression之間印出空白來,這樣會讓結果好看一點。以下所示:
>>> i = 256*256
>>> print 'The value of i is', i
The value of i is 65536
若是不想每次的輸出都換行的話能夠在 print 敘述以後加上逗號,以下所示:
>>> 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
值得注意的是若是最後一行尚未完成的話,直譯器會在印出prompt以前印出新的一行。
5 更多流程控制的工具
除了咱們剛剛介紹的 while 敘述以外,Python也可以使用大部分其它程序語言使用的流程控制形式 ─ 除了有一些不一樣以外。
5.1 if 敘述
大概最爲人所知的 statement 就是 if 敘述了,舉例以下:
>>> x = int(raw_input("Please enter a number: "))
>>> 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 部分能夠有一個也能夠沒有。 `elif' 這個關鍵詞是`else if'的簡化,並且有減小過度縮排的效果。 用 if ... elif ... elif ... 這樣的寫法能夠來取代在其它一些程序語言中常見的 switch 或是 case 的寫法。
5.2 for 敘述
在Python裏的 for 敘述的用法與在C或是Pascal裏的用法有所不一樣。不像是在Pascal中必定要執行某個數目的循環,也不像是在C中讓使用者決定執行的進度(step)及結束執行的條件,Python的 for 敘述會將一個系列(sequence,像是list或是string)裏全部的成員走遍一次,執行的順序是依照成員在squence裏的順序。如下是一個例子:
>>> # Measure some strings:
... a = ['cat', 'window', 'defenestrate']
>>> for x in a:
... print x, len(x)
...
cat 3
window 6
defenestrate 12
在循環的執行之中改變sequence的內容是危險的一件事(固然,只有可變的sequence像list才能做更動),若是你真的須要在循環的執行中改變list的成員值,最好先複製一份這個list的拷貝,而後針對這個拷貝來作循環。list的切割(slice)提供了一個簡便的製做拷貝的方法:
>>> for x in a[:]: # make a slice copy of the entire list
... if len(x) > 6: a.insert(0, x)...
>>> a
['defenestrate', 'cat', 'window', 'defenestrate']
5.3 range() 函式
若是你真的須要一個循環執行必定數目的次數的話,你能夠使用內建的 range() 函式。這個函式會產生一個含有逐步增長數字的list。以下:
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
在這個函式中的所傳入的參數是表明端點,並且這個端點不在產生的list之中。 range(10) 正好產生10個數值,正好是這個list的index是由0到10。咱們也可讓這個產生的list從某個數值開始,或者規定其每次增長的數值爲多少 (增長值也能夠是負數,這個增長值也叫作 `step')。
>>> range(5, 10)
[5, 6, 7, 8, 9]
>>> range(0, 10, 3)
[0, 3, 6, 9]
>>> range(-10, -100, -30)
[-10, -40, -70]
因此若是咱們要循環一次一個sequence的index的話,咱們能夠用 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
5.4 break 及 continue 敘述,以及在循環中的 else 子句
如同在C語言裏同樣, break 敘述中斷最靠近的一個 for 或 while 循環。
一樣的,從C語言借過來的 continue 敘述會中斷目前執行的循環,而且執行下一個循環。
特別的是,Python的循環有一個 else 子句,這個子句以後的程序代碼會在整個循環正常結束的時候執行,(對 for) 循環而言指的是list已經到底,對 while 循環而言指的是條件式變成false)。可是,如果在非正常結束(由於 break 敘述)的狀況下 else 子句的程序代碼就不會執行。底下的例子是一個循環,用來找出全部的質數:
>>> 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
5.5 pass 敘述
pass 敘述什麼也不作,一般是用在當你的程序的語法上須要有一個敘述,可是卻不須要作任何事的時候。例子以下:
>>> while 1:
... pass # Busy-wait for keyboard interrupt
...
5.6 定義函式
咱們能夠定義一個函式,在底下這個函式定義的例子,當咱們給定想要印出的範圍,這個函式會印出一個費氏數列來:
>>> def fib(n): # write Fibonacci series up to n
... "Print a Fibonacci series up to n"
... a, b = 0, 1
... while b < n:
... print b,
... a, b = b, a+b
...
>>> # Now call the function we just defined:
... fib(2000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
在上例中這個 def 關鍵詞表明瞭一個函式的定義(function definition),在 def 以後必須接着函式的名稱以及一個用括號括起來的一連串的參數。接下來一行以後的程序代碼就是函式的主體部分,並且必須是縮排的。函式的程序代碼部分的第一個statement能夠是一個字符串常數(string literal),這個字符串常數會被看成是函式的批註部分而叫作批註字符串(documentation string或是 docstring )。
有工具能夠使用這個批註字符串來自動的製做出線上的或是印出來的文件,或者是讓使用者能夠交互式的瀏覽程序代碼。寫批註是一個好習慣,因此最好養成這個好習慣,把全部的程序代碼都寫上批註字符串。
執行函式的時候會產生一個目前(local)的符號表(system table),這個表是用來記錄函式中的全部local的變量的。更精確的來講,全部在函式中變量的設定值都會紀錄在這個system table中,因此當你要使用(reference)一個變量時,會先檢查local的system table,而後是整個程序(global)的system talbe,而後是內建的變量名稱。雖然 global 變量能夠在函式使用(reference),可是不能在函式以內直接的設定其值(除非是在一個global的statement中創建的)。
當函式被呼叫時,實際傳入的函式參數是會被紀錄在被呼叫函式的local system table裏的。所以,參數被傳入時是 以其值傳入的(call by value) 。在此的值指的是對象的參考( reference ),而非對象自己的 值。 4.1 當一個函式呼叫另外一個函式時,就會所以呼叫而創建一個新的local system table。
當定義函式的時候,也就在目前所在的system table裏定義了這個函式的名稱。對直譯器來講,這個函式名稱的數據型態是一個使用者自訂的函式。這個函式的值名稱能夠被設定給另外一個名稱,而後這個新的名稱就能夠被看成是一個函式名稱來使用。這個過程就是一個通常的從新命名的機制。
>>> fib
<function object at 10042ed0
>>>> f = fib
>>> f(100)
1 1 2 3 5 8 13 21 34 55 89
你也許認爲 fib 不是一個函式(function)而是一個程序(procedure)。如同在C中同樣,在Python的procedure指的是沒有傳回值的函式(function)。事實上,就技術上而言,procedure也是有傳回值的,只是所傳回的是一個Python系統內鍵的值,叫作 None 。一般來講,若是隻傳回 None 的話,直譯器不會印出這一個傳回值。可是,若是你真想看一看它的話,你能夠這樣作:
>>> print fib(0)
None
若是想讓你的函式傳回一個包含費氏數列的list,而不是隻印出來的話,實際上是很簡單的:
>>> def fib2(n): # return Fibonacci series up to n
... "Return a list containing the Fibonacci series up to n"
... result = []
... a, b = 0, 1
... while b < n:
... result.append(b) # see below
... a, b = b, a+b
... return result
...
>>> f100 = fib2(100) # call it
>>> f100 # write the result
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
如同往例,這個例子事實上示範了一些新的Python的特色:
? return 敘述使得函式傳回了一個值。若是單單 return 沒有其它的expression來表示傳回的值時,就表示這是一個從procedure傳回來的寫法(procedure到結束都沒有傳回值也是表示從procedure傳回來)。這種寫法表示傳回值是 None 。
? result.append(b) 這個敘述表示呼叫了 result 這個list對象的一個方法( method ) 。Method是一個特別」屬於」某個對象的函式,並且其名稱的形式是 obj.methodname 。在這裏 obj 指的是某一個對象(咱們也能夠用expression來代替),而 methodname 指得是由這個對象的數據型態所定義的這個方法的名稱。不一樣的數據型態會定義不一樣的方法,不一樣的數據型態也許所定義的方法名稱會相同,可是並不會形成衝突(你能夠定義你本身的數據型態及其方法,咱們稱之爲類別( classes ),後面會再談到的)。在這個例子裏的 append() 方法是在list這個數據型態中定義的。這個方法會在list的最後面加入一個新的成員,在這個例子裏也能夠寫做 " result = result + [b]" ,效果同樣,可是用方法來寫有效率多了。
5.7 定義函式(續)
在定義函式的時候咱們能夠加入不定數目的參數,加入參數的寫法有三種,是能夠混和使用的。
5.7.1 預設內定參數值
最好用的一種寫法是,對其中的一個或多個參數給它一個特定的默認值。這樣子的話,當你在呼叫函式時,就能夠不用傳入參數,或是傳入較少的參數了。請看下例:
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
當你呼叫這個函式的時候你能夠用 ask_ok('Do you really want to quit?') ,或者是 ask_ok('OK to overwrite the file?', 2) 。
設定的默認值能夠是一個變量,可是這個變量在函式定義的時候就以定義時的狀況( defining scope )決定(evaluate)了其值,因此如下的例子:
i = 5
def f(arg = i): print arg
i = 6
f()
印出的結果會是 5 。
重要的警告: 這個參數默認值只有被evaluate一次,這在當默認值是可變的對象像是list或是dictionary時會形成重要的差異。舉例來講,底下的函式會記錄曾經被呼叫過每次所傳入的參數。
def f(a, l = []):
l.append(a)
return l
print f(1)
print f(2)
print f(3)
印出來的結果會是:
[1]
[1, 2]
[1, 2, 3]
因此若是你的默認值是一個可變的對象,可是你又不想讓每次呼叫都共享的時候,你就必須如此寫你的函式:
def f(a, l = None):
if l is None:
l = []
l.append(a)
return l
5.7.2 關鍵詞參數
呼叫函式時也能夠使用關鍵詞參數,其形式是 " keyword = value" ,底下的這個函式:
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, "!"
用這些方式呼叫都是正確的:
parrot(1000)
parrot(action = 'VOOOOOM', voltage = 1000000)
parrot('a thousand', state = 'pushing up the daisies')
parrot('a million', 'bereft of life', 'jump')
可是用這些方式都是不正確的:
parrot() # required argument missing
parrot(voltage=5.0,'dead')#non-keyword argument following keyword
parrot(110, voltage=220) # duplicate value for argument
parrot(actor='John Cleese') # unknown keyword
通常來講,一連串的參數的次序是先有非關鍵詞參數(也能夠沒有)而後纔是關鍵詞參數,關鍵詞必須是函式定義時所用的參數名稱。這個定義時用的參數名稱有沒有默認值並不重要,可是一個傳入的參數只能有一個值(默認值不算),若是你已經先用非關鍵詞參數給了某個參數一個值,接下來你就不能再用關鍵詞參數給它另外的值。底下的例子就違反了這個規則:
>>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: keyword parameter redefined
當一個函式定義時參數名稱是以 ** name 這種形式定義時,表示這個參數要接受的是一個 dictionary(譯:字典,包含許多關鍵詞以及值的對應),這個 dictionary 包含許多的關鍵詞參數,可是這些關鍵詞不能跟其它的參數名稱相同。另外參數也能夠用 *name 這種形式定義(下一小節會解釋),這種方式定義的參數要接受的是一個 tuple(譯:不可更動的list),這個 tuple 能夠接受不限數目的非關鍵詞參數( *name 必需要出如今 **name 以前)。下面的例子就是一個函式定義的範例:
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]
要呼叫這個函式,你能夠這樣呼叫:
cheeseshop('Limburger', "It's very runny, sir.",
"It's really very, VERY runny, sir.",
client='John Cleese',
shopkeeper='Michael Palin',
sketch='Cheese Shop Sketch')
函式執行的結果以下:
-- 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
5.7.3 隨意的參數串
最後,咱們要介紹最不常見的形式,也就是定義一個函式能夠接受任意數目的參數,這些傳入的參數會被放進一個tuple裏面去。在這一個任意數目的參數以前,能夠定義沒有或是一個或是多個普通的參數:
def fprintf(file, format, *args):
file.write(format % args)
5.7.4 Lambda 形式
因爲衆多的需求,Python裏面也加入了這一個在其它功能性程序語言及Lisp裏面常見的特性。你能夠使用 lambda 這個關鍵詞來定義一些小的沒有名字的函式。底下是一個傳回兩個參數值相加結果的例子: "lambda a, b: a+b" 。Lambda形式能夠使用在任何須要函式對象(function objects)的地方。語法上限制lambda形式只能有一個expression,其功能只是方便的取代一個正常的函式定義。就像是函式裏面包含函式定義同樣,lambda形式不能使用(reference)外面一層函式的的變量,可是你能夠使用傳入默認值參數的方式來克服這個問題,像是下面的例子:
def make_incrementor(n):
return lambda x, incr=n: x+incr
5.7.5 批註字符串
批註字符串的內容及形式是有一個新的約定俗成的規範的。
第一行應該是一個有關這個對象的目的的短的、簡潔的摘要。由於簡潔的緣故,這一行不該該包括對象的名稱及型態(除非對象的的名稱正好是解釋對象目的的一個動詞),由於對象名稱及型態是能夠從其它地方得知的。這一行第一個字的第一個字母應該大寫,最後應該有一個句點。
若是批註字符串還包含其它行的話,第二行應該是空白的,這樣可讓摘要及細部的解釋有所區分。底下的各行應該是一個或多個段落,其目的應該是諸如解釋對象的呼叫方法及其副效果(side effects)的解釋說明。
通常時候,Python的分析器(parser)並不會把多行字符串的縮排拿掉,可是在批註字符串中,批註字符串的處理工具須要特別拿掉那些縮排。底下的通常通用準則能夠用來幫助決定批註字符串如何縮排:在第一行以後所遇到的第一個非空白行決定了整個批註字符串的縮排大小,(咱們不能用第一行,由於第一行必定要跟着引號在一塊兒,因此其縮排是不明顯的)。在這以後的與這個縮排相等的空白,都會被整個拿掉。若是某行的前面有空白但縮排的空白不足(這是不該該發生的),這些縮排也會被整個拿掉。空白的解釋是把tab展開後(通常爲八個空白)的方式來解釋的。
這裏示範瞭如何使用多行的批註字符串:
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print my_function.__doc__
Do nothing, but document it.
No, really, it doesn't do anything.
腳註
... 對象自己的值。4.1
事實上是,較洽當的說法是以其對象參考傳入的( call by object reference ),由於若是一個不可改變的對象傳入以後,呼叫這個函式的地方仍然能夠看到這個函式對這個對象的改變(例如在list之中插入一個對象)。
6 數據結構
這一章討論的內容有些你在以前已經看過,但咱們會更深刻的討論。另外,咱們也會介紹一些新的東西。
6.1 列(Lists)(續)
列(list)這個數據型態有一些方法能夠使用,底下咱們就列出來一些經常使用的方法:
append(x)
在list的尾端加入一個成員,也能夠用這個方法來寫 a[len(a):] = [x] 。
extend(L)
接受一個新的list的參數,而後把它加入到目前這個list的尾端,也能夠寫做 a[len(a):] = L 。
insert(i, x)
在某個特定的位置加入一個成員。第一個參數是要加入的位置的index,因此 a.insert(0, x) 會加入在list的最前端,而 a.insert(len(a), x) 會在最後端加入,相等於 a.append(x) 。
remove(x)
拿掉第一個其值相等於 x. 的成員。若是整個list都沒有這個成員,那就會獲得一個錯誤(error)。
pop([i])
從一個list中拿掉某個位置的成員,而且傳回這個被拿掉的成員。若是沒有傳入位置的index的話, a.pop() 會傳回這個list的最一個成員,一樣的這個成爲會被從這個list之中拿掉。
index(x)
傳回第一個其值相等於 x 的成員之位置(index),若是整個list都沒有這個成員,那就會獲得一個錯誤(error)。
count(x)
傳回在整個list裏面, x 出現了多少次。
sort()
針對list裏面的成員作排序。
reverse()
反轉整個list裏面成員的位置。
底下的這個例子使用了大部分的lsit的方法(method):
>>> 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]
6.1.1 把列(Lists)看成堆積(Stacks)使用
因爲有這些好用的方法,把列(list)看成堆積(stack)來使用是一件容易的事。Stacks的最後一個加入的成員是第一個被取出來的成員(後進先出``last-in, first-out''法則)。要在stack的最頂端加入一個成員能夠使用 append() ,要從stack的最頂端取出一個成員能夠用 pop() (不須加入參數)。例子以下:
>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]
6.1.2 把列(Lists)看成隊列(Queues)使用
你也能夠很方便的拿list來看成隊列(queues)來使用。Queues的特色是第一個加入的成員會是第一個被取出的成員(先進先出``first-in, first-out''法則)。要在queue的後端加入一個成員能夠使用 append() ,要從queue的最前端取出一個成員能夠使用 use pop() ,記得參數是 0 。例子以下:
>>> queue = ["Eric", "John", "Michael"]
>>> queue.append("Terry") # Terry arrives
>>> queue.append("Graham") # Graham arrives
>>> queue.pop(0)
'Eric'
>>> queue.pop(0)
'John'
>>> queue
['Michael', 'Terry', 'Graham']
6.1.3 功能式程序設計工具
有三個與list合用很是有用的內建工具函式: filter(), map(), 以及 reduce() 。
"filter( function, sequence)" 這個函式會傳回 一個sequence (若是可能的話其成員爲同一數據型態),這個sequence裏面的成員都是將 sequence 裏面的的成員,一一傳入到 function( item) 所表明的函式後,傳回值爲true的成員所組合而成。這個函式對於傳入的 sequence 有過濾的效果,以下例所示:
>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]
"map( function, sequence)" 會針對 sequence 裏的各個成員呼叫 function(item) ,而後傳回個別成員呼叫以後傳回的結果。舉例來講,要計算一連串的立方值,咱們能夠如此作:
>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
咱們也能夠傳入不僅一個sequence。若是傳入多個sequence的話,第一個函式名稱的參數要是可以處理多個參數的函式,而後系統會把各個sequence相對應的成員拿出來放入函式之中(若是兩個sequence長度不相等的話,不足的會用 None 來補足)。若是第一個函式名稱參數爲 None 的話,所呼叫的函式就僅僅是傳回其所傳入的參數。綜合以上的兩個特性,咱們能夠使用 " map(None, list1, list2)" 這一個工具函式來方便的轉換兩個sequence成爲一個成對的成員組合的sequence。請看例子:
>>> 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( func, sequence)" 會利用 sequence 的前兩個成員當參數呼叫 func ,而後所得的傳回值再與下一個成員當參數傳入 func ,一直到整個 sequence 結束。下面的例子計算1到10的總和:
>>> def add(x,y): return x+y
...
>>> reduce(add, range(1, 11))
55
若是在 sequence 裏面只有一個成員的話,這個成員的值就會直接傳回。若是在 sequence 裏面沒有任何成員的話,會形成一個例外情況(exception)。
咱們也能夠加入第三個參數來看成開始的值,如此當傳入的 sequence 是空的話,就能夠使用這個開始值。若是是正常的sequencde的話,開始值會先跟第一個成員被傳入看成呼叫 func 的參數,其傳回值再跟第二個成員傳入 func ,依此類推。請看下例:
>>> def sum(seq):
... def add(x,y): return x+y
... return reduce(add, seq, 0)
...
>>> sum(range(1, 11))
55
>>> sum([])
0
6.1.4 傳回整個列 (List Comprehensions)
List comprehensions提供了一個製造list簡潔的方法,而不用使用 map(), filter() 以及/或者 lambda 形式。其結果也比使用以上的方法來作出list要來的清楚易懂。list comprehension一般是一個expression跟着是一個 for 的語句,而後是零個或多個 for 或是 if 語句。其傳回的list是一個由在 for 及 if 語句條件下執行expression的結果。若是expression的結果是一個tuple,就必須用括號"( )"括起來。
>>> freshfruit = [' banana', ' loganberry ', 'passion fruit ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
>>> vec = [2, 4, 6]
>>> [3*x for x in vec]
[6, 12, 18]
>>> [3*x for x in vec if x > 3]
[12, 18]
>>> [3*x for x in vec if x < 2]
[]
>>> [{x: x**2} for x in vec]
[{2: 4}, {4: 16}, {6: 36}]
>>> [[x,x**2] for x in vec]
[[2, 4], [4, 16], [6, 36]]
>>> [x, x**2 for x in vec] # error - parens required for tuples File "<stdin>", line 1
[x, x**2 for x in vec]
^
SyntaxError: invalid syntax
>>> [(x, x**2) for x in vec]
[(2, 4), (4, 16), (6, 36)]
>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]
>>> [x+y for x in vec1 for y in vec2]
[6, 5, -7, 8, 7, -5, 10, 9, -3]
6.2 del 敘述
del 敘述可讓你輕鬆的去掉在list當中某一個位置(index)的成員。這個敘述也能夠用切割(slice)的方法來去掉某一段的成員(在以前咱們必須藉着設定某個slice爲空list來達成一樣的效果)。請看下例:
>>> 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 的例子。
6.3 Tuples(固定有序列)及Sequences(有序列)
咱們以前所討論的lists以及字符串(strings)有不少的共通點,例如能夠用index來定位置,能夠切出其中的某一段(slicing)等等。事實上,list及字符串都是 sequence 這個數據型態的特例。因爲Python是一個能夠不斷進步的語言,其它的sequence數據型態有可能會陸續的加入。咱們就來看另一種標準的sequence數據型態:固定有序列( tuple )。
一個tuple是由特定數目的值所組成,其成員與成員之間以逗號分開。舉例以下:
>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
如同在前面例子所見到的,tuples輸出的結果都會包含在括號之中。因此,巢狀tuple(tuple之中有tuple)能夠被清楚的區分出來。Tuple在輸入的時候能夠有括號也能夠沒有,一般咱們都會加上括號(特別是用在在複雜的expression之中)。
Tuples有不少種用途,例如(x, y)座標,從數據庫取出的員工的數據庫記錄等等。Tuples跟字符串同樣都是不可改變(immutable)的:咱們不能單獨的設定一個tuple裏面的個別成員(雖然咱們能夠用切割及連結(concaatenation)來達到一樣的效果)。咱們也能夠作出能夠改變成員的tuple來,例如list。
有一個特殊的狀況就是隻包含0個或1個成員的tuple:要創造這樣的一個tuple,咱們必須在語法上有一些的變化。空的tuple的表示方法是一對空的括號,只有一個成員的tuple表示方法是在成員後面加上逗點(不能只是用括號把一個成員括起來)。雖然有點奇怪,可是蠻有用的。請看例子:
>>> empty = ()
>>> singleton = 'hello', # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)
t = 12345, 54321, 'hello!' 這個敘述是一個tuple包裝( tuple packing )的例子: 12345 , 54321 以及 'hello!' 這三個值都被包裝放在一個tuple裏面了。咱們也能夠使用相反的操做方式,例如:
>>> x, y, z = t
這個動做叫作打開sequence( sequence unpacking )。Sequence unpacking的動做須要在設定符號左邊有一串的變量,其數目應與右邊sequence的成員數目相同。值得注意的是,多重設定(a, b = 1, 2)其實只是tuple packing以及sequence unpacking的結合罷了!
有一個不太對稱的地方:packing的動做永遠結果是一個tuple,可是unpacking能夠針對各類不一樣的sequence來作。
6.4 Dictionaries(字典)
另一個在Python當中很好用的內建數據型態是字典( dictionary )。Dictionary有的時候在別的程序語言裏面也叫作連結記憶( ``associative memories'' )或者是連結數組( ``associative arrays'' )。不一樣於sequence是由一連串的數字來作index,dictionary用一個特殊的不可改變的(immutable)鑰( keys 來看成其 index。字符串及數字都不能被改變,因此均可以來看成dictionary的key。Tuple若是隻含有字符串,數目字,以及其它tuple的話也能夠看成key。若是tuple裏面有包含任何可改變的(mutable)的對象的話(包括直接或間接),就不能看成key來使用。List不能看成key,由於list的成員能夠被改變(你能夠用 append() 以及 extend() 之類的方法,或是切割(slicing) 或 index 來設定list的個別成員)。
咱們最好把dictionary想象成一個沒有順序的 key: value 成對的組合。惟一的條件是,在dictionary裏面key的值必須是惟一不重複的。最簡單的dictionary就是一對空的中括號: {} 。在中括號裏面放入由逗號分開的key:value對,就成了dictionary裏面的成員。這也是當dictionary被印到輸出時的標準格式。
咱們能夠對dictionary作一些事,包括加入一個帶有key的值、或者是用key來找一個特殊的值。咱們也能夠用 del 來刪去一對key:value的成員。若是你試圖存一對key:value可是這個key已經被使用了的話,原來的那一個value的值就會被蓋過。若是你想用一個不存在的key來找出某一個成員的話,你會獲得一個error。
使用 keys() 這一個dictionary的方法咱們能夠獲得一個由全部的key值組成的list,其順序是隨機沒有次序的(若是你想要排序的話,只要針對這一個獲得的list來呼叫其 sort() 方法就能夠了)。要檢查某個key是否是存在的話,你能夠使用 has_key() 這一個method來作檢查。
底下是一個有關dictionary的小例子:
>>> 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
6.5 條件(續)
在以前所談到的 while 及 if 裏面的條件敘述,除了通常的比較以外也能夠包含其它的運算。
咱們能夠用 in 以及 not in 來檢查某個值是否出現(或沒出現)在某個sequence裏面。咱們也能夠使用 is 以及 is not 來檢查兩個對象是否事實上指的是相同的一個對象(這隻有跟像是list同樣可變的對象有關)。全部的比較運算的運算優先次序都是同樣的,都比全部的數字運算要來的低。
比較運算是能夠連起來的:像是 a < b == c 就是試驗是否 a 比 b 小,以及 b 和 c 是否相等。
比較運算也能夠用 and 以及 or 等boolean運算來連結起來,其比較的結果(或其它boolean運算的結果)也能夠用 not 來獲得相反(negated)的結果。在這些運算裏, not 有最高的優先次序, or 的優先次序最低,可是它們全部的優先次序都比比較運算來的低。因此, A and not B or C 其實相等於 (A and (not B)) or C 。固然,最好適時的使用括號來幫助你表達你真正想要的組合。
and 以及 or 這兩個boolean操做數也能夠稱作有快捷方式的操做數( shortcut operators):它們的evaluated的次序都是由左而右,並且一但已經能夠決定其運算的結果,就不會再繼續的作下去。也就是說若是 A 以及 C 都是 true 而 B 是false的話, A and B and C 並不會evaluate C 這個expression。通常來講這些shortcut operator 的傳回值若是不是看成boolean而是看成通常的值來用的話,其傳回值會是最後一個被evaluate的expression的值。
咱們也能夠把一個比較運算,或是 boolean運算的結果設定給一個變量,其例子以下:
>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim'
值得注意的是不像是C,在Python裏面設定(assignment)不可以放在expression裏面。C的程序設計師也許會針對此點抱怨,可是這樣的好處是能夠避免一些常見的把設定( = )及等於( == )弄混淆的情形
6.6 Sequences(有序列)及其它數據型態的比較
Sequence 對象能夠和其它的相同數據型態的sequence 對象相比較,其比較方法是依照所謂的 lexicographical 順序(lexicographical ordering)。首先是兩個sequence的第一個成員互相比較,若是比較有大小之別的話就此決定其相對大小,如果相等的話就再比較下一個成員的大小,餘此類推直到sequence的結束。若是兩個要相比較的成員自己也是一個sequence的話,一樣的條件能夠繼續遞歸的使用在這兩個sequence之上。若是這兩個sequence的全部成員都相等的話,咱們就說這兩個成員是相等的。若是某一個sequence是另外一個sequence的一部份的話,較短的那一個sequence就是較小的。字符串的Lexicographical順序用的是個別字符的ASCII碼的順序。底下是一些同一數據型態的sequence的比較例子:
(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)
值得注意的是,咱們也能夠 比較兩個不一樣數據型態的對象,而其結果是依其數據型態的名稱來決定的。因此全部的list都比字符串還要來的小(由於list小於string),全部的string也都比tuple還要小。至於數值的數據型態則是由其數值大小來決定其大小,因此0等於0.0的,其他按此類推。
5.1 Footnotes
... 其他按此類推。5.1
你不該該徹底倚賴這些比較不一樣數據型態的規則,由於這些規則是還沒有肯定的,之後的Python版本也有可能會再作更動。
7 模塊
若是你離開Python直譯器而後又再打開Python直譯器的話,你會發現你剛纔定義的一些東西(函式或變量)都再也不存在了。因此說,若是你真的想寫一些比較大型的程序的話,你可能須要有一個文字編輯器來編輯一個檔案,而後再讓Python直譯器來將這個檔案看成輸入(input)來處理。這個過程就是寫腳本( script )的過程。若是你的程序繼續的愈來愈長的話,你也許會想要把你的程序分紅幾個小的檔案,這樣比較方便來維護你的程序。你也許也會但願有一些方便的函式可讓你自由的用在好幾個程序之中,你又不想要copy這些函式的定義在每一個程序之中。
要達到以上的這些目的,Python有一個將定義放在檔案中的方法,你能夠以後再在你的script或是互動模式的程序下使用這些存好的定義。這樣的檔案就叫作模塊( module )。存在於module之中的定義能夠用 imported 放入在其它的module或是主要的 main module之中。(main module是一組你能夠在script的最高一級 (top level)部分使用,或是在互動模式中使用的變量)。
一個module就是一個包含有Python的定義及敘述的檔案,檔案的名稱就是module的名稱加上延伸檔名 .py 在後面。在一個module裏面,module的名字(是一個字符串)會存在 __name__ 這個變量裏面並看成全域變量(global variable)使用。舉例來講,你能夠用你喜歡的文字編輯器打入如下的內容,並將這個檔案存在目前的目錄,並取名爲 fibo.py :
# Fibonacci numbers module
def fib(n): # write Fibonacci series up to n
a, b = 0, 1
while b < n:
print b,
a, b = b, a+b
def fib2(n): # return Fibonacci series up to n
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
如今你能夠進入Python的直譯器裏面而且import 你剛剛創建的module,其方法以下:
>>> import fibo
這個命令並不會使得全部的 fibo 裏面的函式名稱都寫入目前的符號表(symbol table)裏面,可是會把 fibo 這個module的名字寫在symbol table裏面。 因此,咱們如今就能夠使用module的名字來呼叫這些咱們以前所定義的函式了:
>>> 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'
若是你真的想要只用函式名稱的話,你能夠把這些函式名稱設定到另外一個local變量去(能夠就是函式的名稱):
>>> fib = fibo.fib
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
7.1 模塊(續)
一個module裏面除了放函式的定義以外也能夠放可執行的敘述(statement)。這些statement的功用在於初始化(initialize)這個module。這些statement也只有在module 第一次 被import的時候纔會被執行。 6.1
每個模塊都有其本身的符號表(symbol table),這個symbol table也就成爲在module裏面所定義的函式的全域變量(global variables)。因此說,寫module的人就能夠自由的使用這些global variable而不須要擔憂會跟module的使用者的global variable有所衝突。從另外一方面來講,若是你知道本身在作什麼的話,你也能夠跟使用函式同樣的使用module裏面的global variable。其語法爲 modname.itemname.
Module能夠被import到其它的module裏面。習慣上(並不是必定),咱們會把全部的 import 的敘述都放在module(或者是script)的最開頭。這樣的話這個被import的module的名稱就會被放在目前這個module的global symbol table裏面了。
有一個變形的方式能夠直接import module裏面的變量或函式的名稱進入symbol table裏面。舉例以下:
>>> from fibo import fib, fib2
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
這樣作的話並不會使得module的名字被放在目前的symbol table裏面。(因此在上面的例子裏 fibo 是沒有被定義的)。
咱們甚至能夠一次將全部的在module裏面所定義的名稱都import進來:
>>> from fibo import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
這一個寫法會import全部的定義的名稱,除了以底線 ( _ ) 開頭的以外。
7.1.1 尋找模塊的路徑
當你import一個叫作 spam 的module時,直譯器會先在目前的目錄尋找一個叫作 spam.py 的檔案,若是沒找到,會再依據定義在 $PYTHONPATH (一個環境變量)裏面的全部路徑來找。 $PYTHONPATH 的語法與設定方法與 $PATH 是同樣的,也就是一連串的目錄路徑的名稱。若是你沒有設定 $PYTHONPATH ,或是在這些目錄當中也找不到的話,直譯器會繼續在一個安裝時預設的目錄來找,在Unix的機器上,一般這個目錄是 .:/usr/local/lib/python 。
事實上,module的搜尋路徑是依照存在 sys.path 這一個變量中的一個許多路徑名稱組成的list。這個變量當在Python直譯器啓動時,會從輸入的script(或目前的目錄)、 $PYTHONPATH 、以及安裝時設定的預設目錄來讀取全部的目錄。若是你知道本身在作什麼的話,你能夠修改這個變量來改變直譯器尋找module的路徑。請參閱以後的標準模塊(standard module)一段。
7.1.2 「編譯過的」( ``Compiled'') Python檔案
對於一些小的程序來講,若是使用不少標準的module,而又想加速啓動的過程,你就能夠用編譯過的Python檔案。好比你要找 spam.py ,若是在你找到這個檔案的目錄裏ey4又有一個叫作 spam.pyc 的檔案的話,這就表示 spam 這個module有一個已經二元編譯過的(``byte-compiled'')的版本能夠使用。在 spam.pyc 裏面也會記錄用來創造它的spam.py上一次被修改的時間,若是 .pyc 裏面所儲存的時間與最新版本的 .py 的修改時間不符合的話, .pyc 檔案就不會被使用。
通常來講,你不須要作任何事來創造一個 spam.pyc 檔案。當你成功的編譯一個 spam.py 檔時,自動的 spam.pyc 文件就會寫入在同一個目錄裏。若是這個過程裏有問題的話,系統不會當這是個錯誤狀況(error)。相反的,若是寫入的檔案沒有徹底成功的寫入的話,這個檔案只會被認爲是不正確的而忽視其存在。 spam.pyc 檔案的內容是獨立於操做系統平臺的,因此一個 Python module 的目錄是能夠被在各類不一樣架構下的多臺機器所共享的。
這裏有一些給專家們的祕訣:
? 當使用 -O 這個選項啓動Python直譯器時,直譯器產生會最佳化程序代碼(optimized code),並存在 .pyo 檔案裏。這個最佳化程序代碼目前並無太多功能,它只是簡單的拿掉全部的 assert 敘述以及 SET_LINENO 指令。當你使用 -O 這個選項時, 全部的 二元碼(bytecode)都會被最佳化,全部的 .pyc 檔都會被忽略,全部的 .py 檔案都會被編譯成最佳化的二元碼。
? 若是你傳入兩個 -O 選項給Python直譯器的話 ( -OO) ,在有些不多見的狀況下會使得編譯器的最佳化過程使得程序沒法正常執行。目前這個選項會使得 __doc__ 字符串從二元碼中被拿掉,進而使得 .pyo 檔案能夠更精簡。可是有些程序會使用到這些字符串,因此你應該只有在你很肯定時才使用這個選項。
? 讀 .pyc 以及 .pyo 檔案並不會比讀 .py file; the only thing that's faster about .pyc or .pyo 檔還要快,惟一的差距是在當被導入(load)時的速度有差異。
? 當你在命令列(command line)使用script的名稱來執行它的話,並不會形成二元碼被寫到 .pyc 或是 .pyo 因此,你能夠把這個script寫成一個module,而後再用一個小的啓動的script來import這個module。這樣能夠減小啓動的時間。事實上,你也能夠直接從命令列啓動 .pyc 或是 .pyo 檔案。
? 你也能夠把 spam.pyc (或是 spam.pyo ,若是你用了 -O 的話) 放在沒有 spam.py 的目錄裏。這樣子,當你給別人你的連接庫時,你能夠給他們比較難用逆向工程(reverse engineer)破解的程序。
? 你能夠用 compileall 這個module來將某個目錄裏面的全部module都便成 .pyc 檔案(或者是 .pyo 檔案,若是你用了 -O )。
7.2 標準模塊
Python包含有一個 標準模塊的連接庫,這個連接庫在另外一個文件 Python Library Reference (Python連接庫參考手冊)中有更多的描述。有些標準模塊已經內建在直譯器裏面,這些模塊讓咱們能夠使用那些不在Python語言自己的一些功能,無論是爲了效率或是要使用操做系統的資源(例如system call)。有些module是在設定時的選項,好比說, amoeba 這個module就只有在你的系統裏面有Amoeba相關的資源時纔會出現。有一個module特別值得咱們好好注意: sys 。這個module在每個Python直譯器裏面都有,其中有兩個變量 sys.ps1 以及 sys.ps2 是用來設定primary prompt 以及secondary prompt的:
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print 'Yuck!'
Yuck!
C>
這兩個變量只有在當你在互動模式下啓動直譯器時纔有定義。
sys.path 這個變量是一個許多目錄路徑組成的list,裏面的目錄路徑就是直譯器尋找module的路徑。這個變量裏面的路徑都是從環境變量 $PYTHONPATH 裏面複製的,或者當 $PYTHONPATH 沒有設定時,就會使用默認值。你也能夠用通常使用list的方法來修改之。例如:
>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')
7.3 dir() 函式
內建的 dir() 函式主要是用來找出某個module裏面所定義的全部名稱。其傳回值是一串通過排序了的字符串list:
>>> 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() 會列出全部你目前已經定義的名稱:
>>> a = [1, 2, 3, 4, 5]
>>> import fibo, sys
>>> fib = fibo.fib
>>> dir()
['__name__', 'a', 'fib', 'fibo', 'sys']
注意這裏的名稱是指全部類型的名稱:包括變量,函式,以及module等等。
dir() 並無列出全部內建的函式及變量的名稱。若是你真想要列出來的話,它們都定義在 __builtin__ 這個標準module裏面:
>>> 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']
7.4 Packages(包裝)
Package是一種用點號表示模塊名稱(``dotted module names'')的組織Python 模塊(module)命名空間的方法。舉例來講:若是module的名稱是 A.B 表示是在 " B" 這個package裏面的一個名稱爲 "A" 的module。就如同使用module使得其它寫module的不用擔憂別人的global variable命名的問題,使用這種帶點號的module名稱也使得寫多個module的package的人不用擔憂所用的module名稱會和別人有所重複。
如今假設你要設計一組的module(就是設計一個package),這個package是用來標準化的處理聲音檔案以及聲音的資料的。因爲聲音檔的格式有不少(一般是由其延伸檔名來辨別,例如 .wav, .aiff, .au) 等格式),你也許須要一個隨時會增長新module的package來處理新的聲音檔格式。因爲你可能想對聲音數據作各類不一樣的處理(例如混音、加回聲、加入平衡方程式,加入人工音響效果等等),因此你還須要寫一些module來專門作這些處理。底下這個架構多是你的package所須要的(用檔案階層系統來表示):
Sound/ Top-level package
__init__.py Initialize the sound package
Formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
aiffread.py
aiffwrite.py
auread.py
auwrite.py
...
Effects/ Subpackage for sound effects
__init__.py
echo.py
surround.py
reverse.py
...
Filters/ Subpackage for filters
__init__.py
equalizer.py
vocoder.py
karaoke.py
...
爲使Python能把這個目錄架構看成是一個package,上面的 __init__.py 這個檔是必需要的。這是爲了要避免有些檔案目錄的名字是很普通的名字(例如 " string" ),這會讓直譯器誤認正確的module名稱而找不到在搜尋路徑中的module。在最簡單的例子裏, __init__.py 能夠是一個空的檔案。可是你也可讓這個檔來作一些package初始化的動做,或者設定 __all__ 這個變量(稍後會再提)。
使用package的人能夠從package裏使用(import)某一個module,例如:
import Sound.Effects.echo
上面的程序代碼會導入(load) Sound.Effects.echo 這個module。若是你要使用這個module,你必須使用完整的名稱,例如:
Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)
另一個導入在package中的某個module的方法是:
from Sound.Effects import echo
一樣的,這會導入 echo 這個moduel。不一樣的是,當你使用這個module的時候你就不用在寫前面package的名稱了。請看如下使用這個module的例子:
echo.echofilter(input, output, delay=0.7, atten=4)
你也能夠直接的import某一個在module裏面的函式或變量,以下例:
from Sound.Effects.echo import echofilter
一樣的,這會導入 echo 這個module,不一樣的是你如今能夠直接的使用 echofilter() 這個函式了:
echofilter(input, output, delay=0.7, atten=4)
值得注意的是當你使用 from package import item 這樣的敘述時,你所import的東西能夠是一個package中的module(或者是subpackage),或者是在module裏面所定義的名稱,例如變量、類別或是函式等等。 import 敘述會先測試是否這個東西真的存在於這個package,若是沒有的話,會假設這是一個module而後試着導入(load)之。若是還失敗的話,就會引起一個 ImportError 的例外情況(exception)。
相反的是,當你使用 import item.subitem.subsubitem 這樣的敘述時,除了最後一個東西(item)之外,其他的都必須是package。最後一個能夠是 module 或是 package ,可是不能是一個定義在module裏面的類別、成員或函式。
7.4.1 從一個Package中Import *
那若是使用者寫了 from Sound.Effects import * ,會形成什麼結果呢?理想情況下,咱們可能會指望會搜尋整個package目錄,而後找出全部的module而且一一的import這些module。不幸的是,在Mac 以及 Windows 平臺下,檔案的名稱大小寫並不統一。因此在這些平臺之上,咱們並沒有法保證 ECHO.PY 這個檔案應該被import成 echo, Echo 或 ECHO (例如,Windows 95 有一個惱人的特色,就是會自動把全部的文件名稱第一個字符大寫)。DOS的 8+3 檔名限制對長的module名稱來講,也是另外一個有趣的問題。
因此惟一的解決方法就是package的做者要提供一個明顯的index給用package的人。若是遵照這個習慣的話,當用package的人在import的時候使用 from Sound.Effects import * 的話,就會去找這個package的 __init__.py 檔案裏面的 __all__ 這個list變量,這個list裏面就會包含全部應該被import進來的module名稱了。身爲Package的做者有責任要保持 from package import * 這個檔案的更新,可是若是package的做者確信沒有人會用 from Sound.Effects import * 這種寫法的話,也能夠不使用這個檔案。舉例來講 Sounds/Effects/__init__.py 這個檔案就能夠有下面這樣的程序代碼:
__all__ = ["echo", "surround", "reverse"]
這就表示 from Sound.Effects import * 會從 Sound 這個package 裏面import 這三個module。
若是沒有定義 __all__ 的話, from Sound.Effects import * 這個敘述就 不會 從 Sound.Effects 這個package裏面import全部的module進入目前的命名空間(namespace)。惟一能保證的是 Sound.Effects 這個package有被imported 進來(可能會執行 __init__.py 裏面的初始化程序代碼),而且這個package裏面所定義的名稱會被import進來。Package裏所定義的名稱包含了在 __init__.py 裏面所定義的名稱(以及所import的module)。固然也包含了在以前用import引進來的module名稱,例如:
import Sound.Effects.echo
import Sound.Effects.surround
from Sound.Effects import *
在這個例子裏,echo以及 surround 這兩個modules 都會被 import進來目前的命名空間(namespace)裏。這是由於當 from...import 這個敘述執行的時候,這兩個module都已經在這個package中有定義了(你也能夠用 __all__ 來定義)。
值得注意的是使用import * 這樣的寫法經常是不被鼓勵的,由於這一般會使得你的程序的可讀性下降。不管如何,在互動模式下這樣作的確會使你減小打太多字的機會,並且有些的module在設計的時候就故意只讓某些特別的名稱能夠被使用。
記住,使用 from Package import specific_submodule 沒有任何不對的地方。事實上,除非你的module的名字會和其它的名稱衝突,不然這是常被推薦使用的形式。
7.4.2 Package內的References(參考)
在package之中的module經常須要彼此互相使用。例如說, surround 這個module就有可能會使用到 echo 這個module裏的東西。事實上,因爲這是最多見的,因此import的時候老是會先找本身這的package裏面的module,而後再依照搜尋的路徑來尋找。所以 surround 這個module能夠使用 import echo 或是 from echo import echofilter 就能夠了。若是在本身所處的這個package裏面找不到這個要import的module的話, import 指令就會在第一級(top-level)的module裏找所指定的名稱。
當一個subpackage是在另外一個package裏的話(例如前面的 Sound ),沒有其它快捷方式可讓你使用其它在同一個外圍的package裏面的subpackage裏的module,你必須使用完整的名稱來指稱你所要用的package。例如說,若是在 Sound.Filters.vocoder 這個module裏面你想要使用在 Sound.Effects 這個package裏的 echo 這個module的話,你就要使用 from Sound.Effects import echo 這個敘述。
腳註
... 纔會被執行。 6.1
事實上,函式的定義也是」被執行」的敘述,這個執行的結果是把函式的名稱寫入module的global symbol table裏面。
8 輸入與輸出
有不少的方式能夠來表現一個程序的輸出結果,能夠印出來在一個可讀的表格裏面,也能夠寫入到檔案裏面供做將來使用。這一章裏面將談到一些可能的方法。
8.1 花俏的輸出格式化
到如今爲止咱們談到了兩種寫入值的方式:用expression的敘述( expression statements ),或是用 print 這個敘述。 (第三種方法是使用file對象的 write() 方法(method),這一個標準輸出所指向的檔案(standard output file),能夠用 sys.stdout 來存取之。請參閱連接庫參考手冊上面對此的詳細說明。)
一般你會但願你對於輸出的結果可以在格式上面稍有控制力,而不僅是預設的用空白連結起來而已。有兩種方法能夠來控制輸出的格式,第一種是本身動手來作字符串的調整。你能夠使用字符串的切割(slicing)以及連結,作成任何你想要的效果。標準的 string module裏面有一些好用的東西,也能夠幫助你填入適當的空白,使字符串的寬度成爲你想要的寬度,咱們待會再來討論如何作。另一個控制輸出格式的方法是使用 % 這個操做數,配合上用字符串成爲左邊的參數。這個操做數會翻譯左邊的這個字符串參數,其功能相似於C裏面的 sprintf() 的字符串參數,而後把右邊要控制的字符串適當的填入,以後再傳回這個格式化的結果。
還有一個問題,如何把其它的值轉換成洽當的字符串呢?幸虧Python裏面的 repr() 函式能夠轉換任何的值成爲一個字符串,你以能夠把這個值寫在反撇號( ` ` )的中間也有一樣的效果。請看一些例子:
>>> x = 10 * 3.14
>>> y = 200*200
>>> s = 'The value of x is ' + `x` + ', and y is ' + `y` + '...'
>>> print sThe value of x is 31.4, and y is 40000...
>>> # Reverse quotes work on other types besides numbers:
... p = [x, y]
>>> ps = repr(p)
>>> ps
'[31.4, 40000]'
>>> # Converting a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = `hello`
>>> print hellos
'hello, world\012'
>>> # The argument of reverse quotes may be a tuple:
... `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),
... # Note trailing comma on previous line
... 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 老是會在每個參數中間加入空白。)
這個例子示範了使用 string.rjust() 的方法,這個函式會使的一個字符串在指定的寬度裏左邊加入空白來向右邊靠攏。另外兩個相相似的函式是 string.ljust() 以及 string.center() 。這些函式自己並無印出什麼東西來,他們只是傳回一個新的字符串。若是傳回的字符串太長了,他們也不會截斷它,他們只是單純的傳回這個新的字符串。這有可能會使你的一欄一欄的格式變成亂七八糟,可是這樣作一般比其它的可能要好不少(可能會形成不正確的結果)。(若是你真想把多餘的部分截掉,你能夠使用一個切割的動做,例如 "string.ljust(x, n)[0:n]" ) 。
另外有一個函式叫作 string.zfill() 這個函式會使的數目字的字符串加入前頭的0。該加入正負號的時候它也會自動加入:
>>> import string>
>> 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.
若是在你的格式化字符串(format string)中有超過一個以上的格式存在,你要在 % 的右邊傳入一個tuple。例如這個例子:
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}
>>> for name, phone in table.items():
... print '%-10s ==> %10d' % (name, phone)
...
Jack ==> 4098
Dcab ==> 7678
Sjoerd ==> 4127
大部分的格式(format)其效果都與你在C裏面所用的同樣,你必需要在右邊傳入適當型態的數據。若是你沒有正確的如此作時,你會獲得一個例外的情況(exception),而不是獲得一個系統核心傾倒出來的內存數據(dump)。其中 %s 這個格式最爲自由,你能夠使用字符串或非字符串,若是你使用非字符串的數據時,數據會自動用內建的 str() 函式轉換成字符串數據。你也能夠使用 * 來傳入一個獨立的(整數)參數來決定寬度或是精確度(precision)的大小。可是,C裏面的 %n 以及 %p 在Python裏面卻沒有支持。
若是你有一個很長的格式化字符串,而你又不想分開他們的話,你能夠使用名稱而非位置來使用這些變量。其方法是使用C格式的延伸形式: %(name)format ,舉例以下:
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
>>> print 'Jack:%(Jack)d; Sjoerd:%(Sjoerd)d; Dcab:%(Dcab)d'%table
Jack: 4098; Sjoerd: 4127; Dcab: 8637678
這個功能當與新的內建函式 vars() 一塊兒使用時特別有用,這個內建函式會傳回一個含有全部local變量名稱及值的dictionary。
8.2 讀寫檔案
open() 這個函式會傳回一個file物件。一般其用法是傳入兩個參數如: "open(filename, mode)".
>>> f=open('/tmp/workfile', 'w')
>>> print f
<open file '/tmp/workfile', mode 'w' at 80a0960>
第一個參數是一個包含文件名稱的字符串,第二個參數是另一個字符串,其內容是一些用來描述你要怎麼使用這個檔案的字符。 mode 能夠是 'r' ,若是你想要這個檔爲只讀的話,也能夠使用 'w' 若是你只想要寫入的話(若是該檔原本就存在的話,你會殺掉原來的檔案),你也能夠用 'a' 表示你要在檔案的尾端加入東西, 'r+' 則會讓這個檔能夠讀也能夠寫。你也能夠不傳入第二個參數,若是沒有傳入 mode 參數的話,會使用預設的 'r' 模式。
在Windows以及Macintosh系統上,你能夠在mode裏面加入 'b' 表示要以二元模式(binary mode)開啓這個檔案,因此你也能夠使用 'rb', 'wb', 以及 'r+b' 。在Windows裏面文字文件及二元文件是有區別的,在文字文件裏面行終止字符(end-of-line)在檔案的讀寫時是自動會被稍稍修改的。這個自動修改的動做對於通常的ASCII文字文件沒有什麼影響,可是會使得像是 JPEGs 或是 .EXE 之類的二元檔被損害。因此當你在處理這些檔案時特別注意要使用二元的模式。(值得注意的是,在Macintosh裏面文字模式的精確的語意是會隨着其背後所用的C連接庫而有不一樣的。)
8.2.1 File對象的Methods(方法)
底下的例子都假設你已經創建了一個叫作 f 的file對象。
若是你想讀一個檔案的內容你須要呼叫 f.read(size) 這個方法(method)。這個method會讀入某個數量的數據,而後將數據以字符串的形式傳回。你也能夠不傳入 size 這個數值參數,若是你沒有傳入或是傳入負值的話,就會將整個檔案都傳回。若是你的檔案比你的內存的兩倍還大的話,這是你本身要處理的問題。其它的狀況下,都會讀入並傳回最可能是 size 數量的字節(byte)的數據。若是已經到了檔案的最尾端你還呼叫 f.read() 的話,回傳值就會是一個空字符串 ("") 。
>>> f.read()
'This is the entire file.\012'
>>> f.read()
''
f.readline() 會一次只讀入一行,換行符號 (\n ) 仍然會被留在字符串的最尾端,而且當檔案不是以換行符號結束時,最後一行的換行符號就會被忽略。這會使得傳回的結果不至於有混淆,當傳回值是空字符串時,咱們能夠頗有信心這已是檔案的最尾端,由於空白的行仍是會有 '\n' 單獨存在的。
>>> f.readline()
'This is the first line of the file.\012'
>>> f.readline()
'Second line of the file\012'
>>> f.readline()
''
f.readlines() 會傳回一個 list ,其內容是全部在檔案內的各個行的數據。若是你傳入第二個無關緊要的 sizehint 參數時,會從檔案內讀入這個參數所表明的byte數目,而且把最後所在的那一整行也一併讀完。這一個方法一般用在一行一行的讀很大檔案時,如此能夠增進讀的效率,並避免在內存中放置大量的數據。只有完整的行纔會被傳回來。
>>> f.readlines()
['This is the first line of the file.\012', 'Second line of the file\012']
f.write(string) 會在檔案內寫入字符串參數 string 所表明的內容,其傳回值是 None 。
>>> f.write('This is a test\n')
f.tell() 會傳回一個整數,表明目前這個file對象在這個檔案內的所在位置,其單元是從檔案開始處有多少個byte。你能夠用 "f.seek(offset, from_what)" 來改變file對象的所在位置, from_what 參數表明從哪裏算起,0表明檔案的最開頭,1表明目前位置,2表明檔案的結尾處。呼叫這個函式file對象會跳到從 from_what 參數表明的位置算起 offset 個byte的距離的地方。若是 from_what 沒有傳入的話,會使用預設的 0,表明從檔案的最開頭算起。
>>> f=open('/tmp/workfile', 'r+')
>>> f.write('0123456789abcdef')
>>> f.seek(5) # Go to the 5th byte in the file
>>> f.read(1)
'5'
>>> f.seek(-3, 2) # Go to the 3rd byte before the end
>>> f.read(1)
'd'
當你已經使用完畢這個file對象時,要記得呼叫 f.close() 把全部由於開文件所使用的系統資源都釋放掉。一但你呼叫了 f.close() 以後,任何的對file對象的動做都會自動的失敗。
>>> f.close()
>>> f.read()
Traceback (innermost last):
File "<stdin>", line 1, in ?
ValueError: I/O operation on closed file
File 對象有一些其它的method能夠用,例如 isatty() 以及 truncate() ,這些比較少用的method能夠參考在連接庫參考手冊裏面有關file對象的說明。
8.2.2 pickle Module(模塊)
從檔案寫入及讀出字符串數據都沒有太大問題,可是數值數據則會比較麻煩。由於 read() 這個method 只傳回字符串,你還得要將這個字符串傳給相似 string.atoi() 這樣的函式來將表明數值的字符串 '123' 轉成數值123。若是你要在檔案內儲存較複雜的數據型態例如lists、dictionaries、或是某個類別的對象時,那就更加複雜了。
爲使使用者不須要本身寫程序來處理儲存這些複雜的數據型態,Python提供了一個標準的module叫作 pickle 。這個使人驚訝的module能夠處理幾乎全部的Python對象(甚至是某些形式的Python程序代碼!),並將之轉換成一個字符串的表現方式。這個過程也叫作 pickling. R。從這個字符串從新組合成咱們所要的對象的過程則叫作 unpickling 。在這兩個過程之間,咱們能夠將這個表明對象的字符串儲存成檔案或數據,或是在網絡上傳給另外一臺機器。
若是你有一個 x 對象及一個能夠寫入的file對象 f ,要pickle一個對象最簡單的方式只要一行程序就能夠了:
pickle.dump(x, f)
若是file對象 f 是可讀的話,要unpickle這個對象只要這樣作:
x = pickle.load(f)
(這個module還有其它的用法能夠pickling多個對象,或是你不想將這個pickled的數據寫入檔案。請參考在連接庫參考手冊內有關 pickle 完整的說明。)
pickle 也是一個標準的方法,能夠將Python的對象儲存起來給其它程序語言使用,或是等待下一次啓動Python再用。技術上來講這叫作 persistent 的對象。由於 pickle 的運用如此普遍,許多的程序設計師專門寫一些Python的延伸功能來處理諸如matrices這些新數據型態的pickle 以及 unpickle的過程。
9 程序錯誤與例外(Exceptions)情形
至此爲止,咱們都只有稍稍的提到錯誤訊息。可是若是你有試着執行上面的範例的話,你可能注意到,基本上錯誤的狀況能夠分紅兩類:語法錯誤 ( syntax errors ) 以及例外狀況 ( exceptions )。
9.1 語法錯誤
語法錯誤也叫作分析時的錯誤(parsing errors),大概是通常在學Python時最多見到的直譯器所發出來的抱怨:
>>> while 1 print 'Hello world'
File "<stdin>", line 1
while 1 print 'Hello world'
^
SyntaxError: invalid syntax
Python分析器(parser)會在印出錯誤的行,而且用一個向上的箭號指出最先發現錯誤的地方,而這個錯誤是發生(至少是被發現)在這個箭號所指的單元(token) 以前。在咱們的例子裏面:錯誤發生在 print 這個關鍵詞,由於前面應該有一個 ( " :" ) 。錯誤信息裏面也包含文件名稱以及行數,因此你能夠很快知道要到哪裏去找錯。
9.2 例外(Exceptions)情形
有的時候,甚至當你的語法徹底正確時,當你執行程序時仍然會出錯。這種在程序執行階段發生的錯誤叫作例外情形 ( exceptions ) ,而且會形成程序致命的終止(沒法執行下去)。你待會就會知道在Python裏面要怎樣處理這樣的情況,可是咱們先來看這樣的情況下會形成什麼錯誤信息:
>>> 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
在這些錯誤信息的最後一行都是解釋到底發生了什麼事。例外狀況(Exception)有不少種類型,類型的名稱也在錯誤信息之中,在上面的例子裏面,exception的類型分別是: ZeroDivisionError, NameError 以及 TypeError. 。對於內建的exception來講,這些印出來的字符串都是這些內建的exception類型的真正類型名稱,可是對於使用者本身自定的exception類型就不必定了(雖然這是一個有用的約定俗成的習慣)。這些標準的exception名稱也正是他們內建的指稱(identifiers) (不是正式的關鍵詞)。
這一行其它部分則是有關這個exception類型的詳細解釋,其意義則是依照exception的類型而有不一樣。
在錯誤信息最後一行以前的部分則是顯示了這個exception發生時的情況,也就是內存堆積(stack)的內容追朔(backtrace)。通常來講這個這個部分包含了stack backtrace的來源行數,可是這並不表明是在從標準輸入讀入時候的行數。
在Python連接庫參考手冊中( Python Library Reference )詳細列出了全部的內建exception及其說明。
9.3 例外(Exceptions)情形的處理
咱們能夠寫一個程序來處理某些的exception。請看下面程序範例,咱們要求使用者輸入一個有效的數字,直到所輸入的數字真正有效爲止。可是使用者也能夠停止這個程序(用 Control-C 或者是任何操做系統支持的方式)。值得注意的是,使用者主動停止程序事實上是使用者引起一個 KeyboardInterrupt 的exception。
>>> while 1:
... try:
... x = int(raw_input("Please enter a number: "))
... break
... except ValueError:
... print "Oops! That was no valid number. Try again..."
...
這個 try 敘述的做用以下:
? 首先,try以後的敘述( try clause ,在 try 及 except 這兩個字之中全部的敘述)都會被執行。
? 若是沒有發生任何exception, except以後的敘述( except clause )會自動被忽略,整個 try 敘述就算是執行完畢。
? 若是當執行try以後的敘述時發生了exception,錯誤地方以後的敘述就不會被執行。而後若是這個exception的類型有某一個適合的 except 關鍵詞以後的類型的話,就會執行這一個except以後的敘述,而後程序從整個 try 敘述以後的地方開始執行。
? 若是所發生的exception在except關鍵詞以後找不到相對應的類型時,系統會將這個類型傳給外面一層的 try 敘述。若是外層的exception處理機制不存在的話,這就是一個沒有被處理的exception( unhandled exception ),而後整個程序會中斷,並出現上面出現的錯誤程序。
一個 try 敘述能夠包含許多的except 部分來處理各類不一樣的exception,可是最多隻有一個handler(譯:exception以後的敘述)會真正被執行。Handlers 只處理在所對應的 try 部分發生的exception,其它的 try 部分發生的exception則不在處理範圍。一個except子句能夠處理一個以上的exception,只要用list括號把它們括起來。例如:
... except (RuntimeError, TypeError, NameError):
... pass
最後的一個 except 能夠不寫出exception 類型的名稱,這就看成是一個外卡(wildcard,譯:處理全部的exception)來使用。當使用時要特別的當心,由於若是你頗有可能就把一個應該被注意的程序錯誤給隱藏起來了。你也能夠在這個except子句裏面印出一個錯誤信息,而後把這個exception再丟(raise)出去(讓呼叫你程序的人來處理這個exception)。
import string, sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(string.strip(s))
except IOError, (errno, strerror):
print "I/O error(%s): %s" % (errno, strerror)
except ValueError:
print "Could not convert data to an integer."
except:
print "Unexpected error:", sys.exc_info()[0]
raise
這個 try ... except 的敘述有一個無關緊要的else子句( else clause )能夠使用,當這個子句存在時,必須是放在全部的except clauses的後面。這個子句裏的敘述是當try子句沒有發生任何exception時,必定要執行的敘述。請看例子:
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except IOError:
print 'cannot open', arg
else:
print arg, 'has', len(f.readlines()), 'lines'
f.close()
使用 else 要比在 try 子句裏面加入多餘的程序代碼來的好,由於這樣減小意外的處理到那些不是由 try ... except 敘述中保護的程序代碼所引起的exception。
當一個exception 發生時,exception自己有一個 連帶的值,也叫作這個exception的參數( argument )。至於這個參數是否存在以及其型態,則是由exception的類型所決定。對於有這個參數存在的exception類型來講,你能夠在except clause的後面加入一個名稱(或是list)來接收這個參數的值。請看下例:
>>> try:
... spam()
... except NameError, x:
... print 'name', x, 'undefined'
...
name spam undefined
若是一個exception 有一個參數的話,當它在沒有被處理,看成錯誤信息印出來的時候,就會成爲最後(詳細解釋(`detail'))的一部份。
Exception的處理者(handlers,exception clause)並不僅處理在try clause當中所發生的exception,也會處理全部在try clause當中所(直接或間接)呼叫的函式所引起的exception。請看下例:
>>> 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
9.4 如何引起例外(Exceptions)
使用 raise 敘述能夠引起一個特定的 exception,例如:
>>> raise NameError, 'HiThere'
Traceback (innermost last):
File "<stdin>", line 1
NameError: HiThere
raise 的第一個參數是想要引起的exception的類型,第二個參數(無關緊要)則是指定這個exception的參數值。
9.5 使用者自訂的例外(Exceptions)
程序設計師能夠本身命名本身想要的excetion,其方法是指定一個字符串給一個變量,或者是本身創造一個新的exception類別來。舉例說明:
>>> class MyError:
... def __init__(self, value):
... self.value = value
... def __str__(self):
... return `self.value`
...
>>> try:
... raise MyError(2*2)
... except MyError, e:
... print 'My exception occurred, value:', e.value
...
My exception occurred, value: 4
>>> raise MyError, 1
Traceback (innermost last):
File "<stdin>", line 1
__main__.MyError: 1
許多標準的module都本身自訂其exception來報回(report)在他們本身所定義的函式裏面所發生的錯誤。
有關於classes 更多的討論請看第九章 ``類別''。
9.6 定義善後的動做
在 try 敘述的機制裏面有一個無關緊要的子句(optional clause),其功用是在定義無論什麼狀況發生下,你都得要作的清除善後的工做。 舉例來講:
>>> try:
... raise KeyboardInterrupt
... finally:
... print 'Goodbye, world!'
...
Goodbye, world!
Traceback (innermost last):
File "<stdin>", line 2
KeyboardInterrupt
這個 finally clause 無論你的程序在try裏面是否有任何的exception發生都會被執行。當exception發生時,程序會執行finally clause以後再引起這個exception。當程序的 try try部分由於 break 或 return 而離開時,finally clause也同樣會在離開的時候(``on the way out'')被執行。
一個 try 敘述機制應該要有一個或多個except clauses,或者是有一個 finally clause,可是不能兩個都有。
10 Class(類別)
Python的類別機制在加入最少新的語法及語意的狀況下加入了類別的支持。Python的類別機制是C++ 以及Modula-3的綜合體。正如同在modules裏面的狀況同樣,Python的 class也沒有在其定義及使用者之間加入絕對的障礙,而是仰賴使用者有禮貌的不要去闖入其定義之中(not to ``break into the definition'')。對於class來講最重要的一些特性在Python裏面都徹底的保留:類別的繼承能夠繼承自個基礎類別(base classes),一個子類別(derived class)能夠override其全部基礎類別(base class)的任何方法(method),一個method也能夠呼叫一個基礎類別的同名方法,對象能夠自由決定是否要讓某些數據是 private的。
以C++ 的術語來講,Python全部的類別成員(包含其數據成員)都是 public 的,並且全部的函式成員(member functions)都是 virtual 的。也並無所謂的建構元(constructors)或是解構元(destructors)的存在。如同在 Modula-3裏面同樣,從對象的方法(method)裏面要使用對象的成員並無快捷方式能夠使用:成員函式的宣告必須在第一個參數中明白的在表示所存在其中的對象,而此參數在呼叫時是不用傳的。如同在Smalltalk裏面同樣,類別自己也是一個對象,事實上在Python裏面,全部的數據型態(data type)都是對象。這提供了在import以及從新命名時候的語意(sematics)。可是如同在C++ 或是Modula-3裏面,內建的基本型態是不能被使用者拿來看成基礎類別使用的。與C++相似但不一樣於Modula-3的是,大部分有特別語法的內建操做數(operators),例如數值運算及subscripting,均可以被拿來在類別中從新定義的。
10.1 術語的使用說明
因爲缺少廣泛性的術語能夠討論類別,我只好偶而從Smalltalk或是C++的術語中借來用。(我其實更想用Modula-3的術語,由於它的術語在語意上比C++還要接近Python,可是我想大部分的讀者都沒有聽過它)。
我也要警告你的是,對象這個字在Python裏面沒必要然指的是類別的一個特例(instance),這是一個在對象導向讀者中常見的陷阱。與C++及Modula-3相同但與Smalltalk不一樣的是,並不是全部在Python裏面的數據型態都是類別,像是整數及list這類的基本的內建型態就不是類別,甚至一些特別的數據型態像是file都不是類別。不管如何, 全部的 Python的數據型態都或多或少都有一些基本相同的語意特性,咱們能夠把這個相同點叫作對象。
對象有其個體性(individuality,獨特性),並且你能夠用不一樣的名字連結到同一個對象去,這在其它的程序語言中也叫作別名(aliasing)。一般你第一次看到Python不會以爲這有什麼特別,並且你在處理不可變更的(immutable)基本型態(例如數目字,字符串及tuple)時,你根本能夠不去管它。可是對於一些均可變更的(mutable)對象,像是list,dictioanry以及其它用來表如今程序以外的實體(像是檔案及窗口)的數據型別,對它們來講aliasing就和與它們有關之Python程序代碼語意的解釋,有(故意的)一些影響。這樣的影響一般是對程序有正面的效益,由於別名(alias)運做的方式就像是一個有禮貌的指標(pointer)。舉例來講,當你傳一個對象當參數時,由於所傳的其實只是一個指標,因此所費的資源就很少。並且,當在函式以內對這個傳入的對象進行修改時,在外面呼叫這個函式的人(caller)會看得見函式所作的修改,這大大的簡化了在Pascal裏面須要兩種不一樣參數傳遞機制才能做到的事。
10.2 Python的可用範圍(Scopes)及命名空間(Naming Spaces)
在介紹類別(class)以前,我首先必須介紹Python有關可用範圍(scope)的一些準則。類別的定義也對命名空間(namespace)作了一些小技巧,因此你須要對scope及namespace的運做有一些瞭解纔可以徹底的掌握到底發生了什麼事。對於進階的Python程序設計師來講,有關這個主題的瞭解是頗有幫助的。
如今讓咱們先來定義一些東西:
一個 namespace 指的是名稱與對象的對應關係的組合。目前來講,namespace都是以Python的dictionary來實做出來的,可是這應該沒有多大意義(除非對程序的效率),並且在將來可能也有所改變。Namespace的例子有:一組的內建名稱(像是 abs() 的函式,還有內建的exception名稱),在module裏的全域變量(global variables),以及在函式裏的local變量。某種意義來講,一個對象裏的特性(attributes,譯:成員)也組成一個namespace。在這裏要知道的重點是,不一樣的namespace裏面所定義的名稱是彼此沒有任何關係的。舉例來講,兩個不一樣的module均可以定義一個叫作``maximize''的函式。這一點都不衝突,由於使用者必需要在這個函式的名稱前加上module的名稱。
喔,我在這裏所用的 attribute 一字指的事全部在點號後面的東西,舉例來講在 z.real 這個expression裏面 real 就是一個屬於 z 這個對象的attribute。嚴格說來,使用module裏面的名稱也是一個attribute的指稱(references),在 modname.funcname 這個expression裏面, modname 就是一個module對象,而 funcname 就是其attribute。在這個例子裏面,恰好module的attributes就對應了在module裏面定義的全域變量,因此咱們就說它們就是在一個namespace裏面。 9.1
Attributes能夠是隻讀的或是可改寫的。對可改寫的attribute,你能夠設定值給它。Module的 attributes是能夠改寫的:因此你能夠寫 "modname.the_answer = 42" 來改變其值。可改寫的attributes也能夠被刪除掉,你能夠用 del 敘述像是 "del modname.the_answer " 來作。
命名空間(Name spaces)是在不一樣的時候被創造出來的,並且其存在的時間也都不必定。內建名稱的namespace是在當Python直譯器啓動時就被創造出來,並且不會被刪除掉。Module裏全域(global)的namespace是當module的定義被讀入的時候就被創造出來,一般在直譯器離開以前也不會被刪除。那些在top-level啓動直譯器裏面被執行的指令,無論是在互動模式或是從script裏面而來的,都隸屬於一個叫作 __main__ 的module,因此它們也算有本身的一個global namespace。 (事實上,內建的名稱也都在一個module裏面,這個module叫作 __builtin__ )
函式全部的namespace叫作local namespace,是在函式被呼叫時才創造的,並且當函式傳回一個值或是引起一個自己沒法處理的exception時,這個namespace就被刪除(事實上,也許說遺忘是比較貼切的形容詞)。固然,遞歸的函式呼叫會使每一個呼叫都有本身的local namespace。
一個可用範圍( scope )是一個在Python程序裏面文字上的範圍,在這個範圍裏面你能夠直接使用某個namespace。直接使用(``Directly accessible'')的意思是指對一個名稱而言不合格的參考(unqualified reference)試圖想要在namespace裏面找某一個名稱。
雖然scope是靜態的(statically)被決定的,可是咱們使用namescope的時候是很動態(dynamically)的來使用之。在任何一個程序執行的地方,都正好有三層的scope正在被使用(也就是有三個能夠直接使用的namespace):首先尋找的是最內圈的scope,包含有local的名稱;其次搜尋的是中間一層,包含有目前所在的module的全域名稱(global names);最後搜尋的是最外面的一層,也就是包含有內建名稱的namespace。
一般,local scope指的是在文字上面目前的函式所擁有的local名稱。在函式以外的話,local scope就指的是global scope所指的namespace。類別的定義在local scope裏面則又放入了另外的一個namespace。
要注意的是scope的決定是依文字的安排來決定的。一個定義在module裏面的函式,其global scope就是module的namespace,無論這個函式是從哪裏或是用哪個別名被呼叫的。在另外一方面來講,真正的名稱搜尋路線是動態的決定的(在程序執行的時候)。可是Python語言自己的定義好像慢慢的往靜態決定變化(也就是在編譯的時候),因此,不要過度依賴動態的名稱解釋。(事實上,local的變量都已是靜態就已經決定了的)。
Python有一個很特別的變化就是當設定(assignment)的時候都必定是進入到了最內層的scope。設定並非複製數據,相反的,它只是把對象及名稱連結起來而已。對於刪除也是同樣的, "del x" 事實上只是把 x 的連結從local scope所表明的namespace中除去。事實上,全部會引進新名稱的動做都是使用local scope:特別是, import敘述以及函式的定義就是把module以及函式的名稱都連結到local scope裏面來了。( global 這個敘述能夠用來特別指定某個特殊的變量是要放在global scope裏的)
10.3 Class(類別)初探
類別(Classes)的觀念引進了許多新的語法,三種新的對象以及一些新的語言上的意義:
10.3.1 9.3.1 定義Class(類別)的語法
最簡單的類別定義的形式看起來像是這樣的:
class ClassName:
<statement-1>
.
.
.
<statement-N>
類別的定義與函式的定義(都是用 def 敘述)相同,都必須在要在它們有任何做用以前就定義好。(你也能夠在 if 敘述或是一個函式裏面放入類別的定義)。
在實務上,存在於類別定義內的敘述一般都是函式的定義,可是咱們也能夠放入其它的敘述。這樣的作法有時也很好用,咱們以後會再會來看這個用法。類別定義內的函式定義一般都有一個特別的參數形式,這是爲了method的特別呼叫習俗的。咱們仍是留到後面再來討論之。
當一個類別的定義進來時,就會創造出一個新的namespace,並且會看成是一個local scope來用。因此全部對local變量的設定都會進入到這個新的namespace裏面。具體來講,函式的定義也會把新的函式的名稱連結到這裏來。
當一個類別的定義正常的離開時( 藉由定義的尾端),一個類別對象( class object )就被創造出來了。這個類別對象基本上來講是隻是一個包裝起來的東西,其內容是由這個類別定義所創造出來的namespace裏面的內容。咱們在下一節就會有更多有關類別對象(class objects)的討論。另外在類別的定義離開時,原來的local scope (在進入類別的定義以前的那一個local space)就會被從新使用,而且這個創造出來的類別對象就會被放在這個local scope裏面,而且被連結到你所定義的類別名稱(上面的例子裏是 ClassName )上面。
10.3.2 類別對象(Class Objects)
類別對象能夠作兩件事情,一是attribute的指涉(references),另外一個是創造出一個特例來(instantiation)。
Attribute references 所使用的是在Python裏面標準的attribute reference的語法: obj.name 。有效的attribute的名稱指的是當類別對象被創造時,全部在類別的namespace裏面的名稱。因此,若是你的類別定義如同下面例子的話:
class MyClass:
"A simple example class"
i = 12345
def f(x):
return 'hello world'
你就能夠使用 MyClass.i 以及 MyClass.f 這兩個有效的attribute references語法,它們分別會傳回一個整數以及一個method對象來。你也能夠設定值給這些類別的attributes,如此你就能夠改變 MyClass.i 的值了。 __doc__ 也是類別對象的一個有效的attribute,其傳回值是這個類別的註釋字符串(docstring),也就是: "A simple example class" 。
類別的特例化( Class instantiation )是使用函式的表示方法。看起來好像這個類別對象是一個沒有參數的函式,而後傳回來的就是這個類別的的一個特例(instance)。咱們再之前面的類別爲例子:
x = MyClass()
就會創造出一個新的類別的 instance ,而後咱們再把這個對象設定給 x 這個local的變量。
類別的特例化(Class instantiation )這個動做(也就是``呼叫''一個類別對象)所創造出來的是一個空的對象。有許多的類別但願創造出來的對象有一個特定的初始狀態,因此你能夠在類別裏面定義一個特別的method叫作 __init__() ,如同下例:
def __init__(self):
self.data = []
當你的類別有定義一個 __init__() method時,當你在特例化(instantiation)你的類別時,就會自動的引起 __init__() 執行,而且創造出一個類別的特例(instance)。因此,一個新的對象就能夠截由底下的呼叫來創造出來:
x = MyClass()
固然, __init__() 這個method 能夠有參數傳入,這樣能夠增長使用時的彈性。在這樣作的時候,使用特例化(instantiate)類別的語法時,所傳入的參數就會被傳到 __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)
10.3.3 特例物件(instance objects)
如今對於這個被創造出來的特例對象(instance objects),咱們又該怎麼用呢?對於這樣的特例對象,惟一它們懂得的就是attribute references。有兩種的attribute names咱們能夠使用:
第一種我叫他是數據特性( data attributes ),這相似於在Smalltalk中所說的特例變量(``instance variables'')以及在C++中的數據成員(``data members'')。如同local變量同樣,Data attributes不須要再宣告,你第一次設定值給它們的時候它們就自動存在了。舉例來講,若是 x 是 MyClass 這個對象的一個instance,底下這個程序代碼就會印出 16 這個結果來:
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print x.counter
del x.counter
第二種instance objecet能夠使用的attribute references叫作方法( methods ) 。一個method就是一個隸屬於某個對象的函式。(在Python中,method一詞並不僅特定用於類別的instances,其它的對象數據型態也能夠有本身的 method,例如list對象就有不少methods像是append,insert,remove,sort等等。可是咱們底下用到method這個詞的時候,除非特別說明,要否則咱們卻是單獨指着instance objects 的method說的。)
一個instance objects 能夠用的有效method名稱是由其類別所決定的。定義上來講,全部類別裏面(使用者定義)爲函式對象的attribute,都會成爲其instance的相對應method。因此在咱們的例子裏, x.f 就是一個有效的method的reference,其緣由是由於 MyClass.f 是一個函式;可是 x.i 就不是一個method的reference,由於 MyClass.i 不是一個函式。可是,要注意的是, x.f 和 MyClass.f 是兩回事,它是一個method物件( method object ),而非一個函式物件。
10.3.4 Method Objects(方法對象)
一般,一個method能夠立刻被呼叫,例如:
x.f()
在咱們的例子裏,這一個呼叫會傳回來 'hello world' 這個字符串。可是,由於 x.f 是一個method對象,因此咱們沒有必要立刻就呼叫它,咱們能夠把它儲存起來,而後再稍後再呼叫它。舉例以下:
xf = x.f
while 1:
print xf()
這個例子一樣的會一直不斷的印出 "hello world" 來。
到底,你的method被呼叫時,什麼事情發生了呢?你也許注意到了 當咱們呼叫 x.f() 的時候並無傳入任何參數,可是咱們在類別定義的時候確實有定義 f 所傳入的參數。究竟是怎麼回事呢?固然,依照Python的定義,當一個函是須要參數而你呼叫時沒有傳入參數的話,是會引起一個例外情況(exception)的,甚至這個傳入的參數沒有被用到也是同樣…
事實上,你也許已經猜到答案了。對於method來講有一個較特殊的事是,method所處的對象會被看成函式傳入的第一個參數。因此在咱們的例子裏面,當咱們呼叫 x.f() 的時候,事實上咱們是呼叫 MyClass.f(x) 。通常來講,若是你呼叫method的時候傳了 n 個參數,其實你是呼叫背後所表明之類別的函式,並且該method所在的對象會插入在傳入的參數中看成第一個參數。
若是你還不瞭解到底method如何運做的話,你也許能夠看看它的實做來更瞭解它。當一個instance的attribute被reference,而這個attribute又不是一個data attribute的時候,該instance的類別會被尋找。若是這個class attribute的名字在類別裏面表明的是一個函式對象的話,就會有一個method對象被創造出來。這個method對象是一個由這個instance對象(的指針),以及剛剛找到的這個函式對象所包裝起來的一個抽象的對象。當這個method對象被帶着一串參數呼叫的時候,這個method對象會先打開原來的包裝,而後會用instance對象(的指針)以及那一串傳進來的參數組成新的參數串,而後咱們再用這個新的參數串來呼叫在method對象裏面的函式對象。
10.4 9.4 一些隨意的想法
[這些東西其實應該更多花點心思加以處理的…]
若是data attributes和method attributes 有相同名稱的話,data attributes 會蓋過method attributes 。要避免這個命名的衝突(這經常是許多bug的由來),你可能須要一些命名的規則。好比說,讓method的名稱都是大寫的,在data attribute的前面加上一些小字符串(或者是底線),或者對於method都用動詞,對data attribute都用名詞。
除了通常object的使用者(client)以外,Data attributes也能夠在method裏面被使用到。也就是說,類別(class)是不能用來實做出純粹的抽象數據型態(abstract data types)的。事實上,再Python裏面沒有東西能夠保證數據的隱藏(data hiding),咱們只能仰賴彼此的約定及尊重了。(另外一方面來講,用C寫成的Python是可能徹底隱藏其實做的細節而且在須要的時候能夠控制對對象的存取權限的;這是用來給C所寫成的Python延伸機制(extension to Python)所使用的。)
使用data attributes的人要特別當心,你有可能把由method所管理的data attributes弄得一蹋胡塗。值得注意的是,類別的使用者能夠自行在instance對象裏面加入data attributes,只要當心處理命名的問題,這不會對method的正確性有所影響。再次提醒,你能夠用命名的規則來避免此事發生。
從method裏面要使用data attributes (或者是其它的methods)並無快捷方式。我發現這樣的好處是程序的可讀性會增長不少,由於當你在讀method的程序代碼的時候,local變量跟instance變量混淆的機會就會少不少。
習慣上,咱們把一個method的第一個參數叫作 self 。這只是一個習慣而已, self 這個名字對Python來講徹底沒有什麼特殊的意義。(可是你要注意,若是你不用這一個習慣的話,對於某些讀你程序的Python程序設計師來講,也許你程序的可讀性就低了一點。並且可能有一些相似像 class browser 之類的程序是靠這個約定來分辨class的特性,因此你不遵照的話,你的類別它們可能就讀不懂)
全部的在類別裏面的函式對象,在定義上都是該類別之instance的一個method。在類別裏面的意思不限定於必定要在文字上是在類別的定義裏面,你也能夠把一個函式對象設定給一個在類別裏面的local變量,這樣也算數的。好比說:
# 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
如今 f, g 以及 h 都是類別 C attributes,並且都指涉(reference)到某個函式物件去。並且,如此作的話,當 C 有instance的時候, f , g 以及 h 都會變成instance的method(事實上 h 所指的函式是跟 g 同一個的)。值得注意的是,若是你真這樣作的話,你只是讓讀你程序的人頭昏眼花罷了。
你也能夠在method裏面呼叫其它的method,你所須要的只是用 self 這個參數的method attribute就能夠了。例如:
class Bag:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
method跟通常的函式對象同樣能夠使用全域名稱(global name)。Method的global scope所指的是類別的定義所存在的module,(注意:類別自己毫不會是一個global scope!)。 你大概不多有機會在method裏面會須要用到global scope,可是你仍是能夠使用global scope的,method能夠使用在global scope之中所import進來的函式以及module,也能夠使用在global scope裏面定義的函式及類別。一般,包含method的這個類別自己就定義在這個global space裏面,並且下一段咱們就要講到爲何你會須要在method裏面用到本身自己的類別。
10.5 繼承(Inheritance)
固然啦,一個程序語言若是沒有繼承的話就不須要擔憂類別(``class'')這個字了。一個子類別(derived class)的定義看起來是這樣的:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
其中,基礎類別的名字 BaseClassName 這個字必須是在子類別所處的scope裏面有定義的。除了直接使用基礎類別的名字以外,你也能夠使用一個expression。這在當你的基礎類別是定義在別的module裏的時候特別有用:
class DerivedClassName(modname.BaseClassName):
子類別定義的執行過程與基礎類別定義的執行過程是同樣的。當一個類別對象被創造出來時,基礎類別也一樣會存在內存中。這是爲了要確保可以找到正確的attribute的所在,若是你的子類別沒有定義某個attribute的話,就會自動去找基礎類別的定義。若是這個基礎類別也是某個類別的子類別的話,這個法則是一直延伸上去的。
子類別的特例化(instantiation)也沒有什麼特別之處,使用 DerivedClassName() 就會創造出子類別的一個新的instance。子類別的method 則是由如下的過程來尋找:會先找該類別的attribute,而後若是須要的話會沿着繼承的路線去找基礎類別,若是找到任何的函式對象的話,這個method的參考(reference)就是有效的。
子類別能夠override基礎類別裏的method。由於method在呼叫本身對象的其它method的時候沒有特別的權限,當一個基礎類別的method呼叫原屬於該基礎類別的method的時候,有可能真正呼叫到的是一個在子類別裏面定義的override的method。(給C++的程序設計師們:全部在Python裏面的method都是 virtual 的。)
一個在子類別裏面override的method也許會須要延伸而非取代基礎類別裏面同名的method,這時候你就須要呼叫在基礎類別裏面的method:你只須要呼叫 "BaseClassName.methodname(self, arguments)" 就能夠了。這對於類別的使用者來講,有時候也是有用的。(注意的是,若是你要這樣作,你須要將基礎類別定義在global scope或是import進來global scope 裏面。)
10.5.1 多重繼承
Python也支持部分的多重繼承形式。一個類別若是要繼承多個基礎類別的話,其形式以下:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
惟一須要解釋的規則是,當你尋找一個attribute的定義時你要如何尋找。其規則是先深,然後由左至右(depth-first, left-to-right)。因此當你要找一個在子類別 DerivedClassName 裏面的attribute卻找不到時,會先找 Base1 ,而後沿着 Base1 的全部基礎類別尋找,若是找完尚未找到的話再找 Base2 及其基礎類別,依此類推。
(也許有些人認爲先左至右而後在深纔對,應該是先找 Base2 及 Base3 ,而後才找 Base1 的基礎類別。若是你這樣想的話,你能夠再想想,當你找 Base1 的時候,你須要先知道這個attribute究竟是定義在 Base1 自己或是其基礎類別裏面,如此纔不會與 Base2 裏面的attribute有同名的困擾。若是你使用先深,然後由左至右的規則的話,就不會有這個困擾。)
你們都知道若是不當心使用的話,多重繼承可能變成在維護程序時的一個惡夢。Python仰賴程序設計師們的約定俗成的習慣來避免可能的名稱衝突。例如一個衆所周知多重繼承的問題,若是一個類別繼承了兩個基礎類別,這兩個基礎類別又分別繼承了同一個基礎類別。也許你很容易就瞭解在這樣的狀況下到底會是什麼情況,(這個instance將會只有一個單一共享基礎類別的``instance variables''或是data attributes),可是很難了解這到底有什麼用處。
10.6 Private變數
在Python裏面只有有限度的支持類別中的private指稱 (class-private identifiers,譯:指變數及函式)。任何的identifier,在以前是以 __spam 這個形式存在的(最前面至少要有兩個底線,最後面最多隻能有一個底線) 如今都要以 _classname__spam 這個形式來取代之。在這裏的 classname 指的是所在的類別名稱,拿掉全部前面的底線。這個名稱的變化不受限於這個identifier其語法上所在的位置,因此能夠套用在定義類別的private instance,類別變量,method,global名稱,甚至用來儲存 其它 的類別instance裏,對目前這個類別來講是private的instance變量。當這個變化過的名稱超過255個字符時,有可能超過的部分是會被截掉的。在類別以外,或者是當類別的名稱只包含底線的時候,就沒有任何名稱的變化產生。
這個名稱的變化主要是用來給類別有一個簡單的方法來定義``private''的instance變量及methods,而不須要擔憂其它子類別裏面所定義的instance變量,或者與其它的在類別以外的程序代碼裏的instance變量有所混淆。注意的是這個變化名稱的規則主要是用來避免意外的,若是你存心要使用或修改一個private的變量的話,這仍是可行的。某方面來講這也是有用的,好比說用在除錯器(debugger)上面,這也是爲何這個漏洞沒有被補起來的一個緣由。(如何製造bug:若是一個類別繼承自某個基礎類別時用了相同的名字,這會使得你能夠從子類別裏面使用基礎類別裏的private的變量。)
值得注意的是,被傳到 exec, eval() 或 evalfile() 的程序代碼並不用考慮引起這個動做的類別是目前的類別,這是相相似於 global 敘述的效果,可是這個效果只限於這個程序代碼是一塊兒被編譯器編譯成bytecode的時候的。一樣的限制也存在於 getattr() , setattr() 以及 delattr(),或是當直接使用 __dict__ 的時候。底下這個例子是一個類別裏面定義了本身的 __getattr__() 以及 __setattr__() 兩個方法,而且把全部的attributes都儲存在private的變量裏面。這個例子適用於全部的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
10.7 其它
有的時候若是有一個像是Pascal的``record'',或者是C的``struct''這類的數據型態是很方便的,這類的數據型態能夠把一些的數據成員都放在一塊兒。這種數據型態能夠用空白的類別來實做出來,例如:
class Employee:
pass
john = Employee() # Create an empty employee record
# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
若是有一段的Python程序代碼須要一個特別的抽象數據型態的時候,一般你能夠傳給這段程序代碼一個相似功能的類別來代替。例如,若是你有一個函式是用來格式化一些來自於file對象的數據,你能夠定義一個類別,類別裏面有相似 read() 以及 readline() 之類method能夠從一個字符串緩衝區(string buffer)讀出數據,而後再把這個類別傳入函式看成參數。
Instance的method對象也能夠有attributes: m.im_self 就是其method爲instance的一個對象, m.im_func 就是這個method相對應的函式對象。
10.7.1 例外(Exceptions)也能夠是類別
使用者自訂的exception不用只是被限定於只是字符串對象而已,它們如今也能夠用類別來定義了。使用這個機制的話,就能夠創造出一個可延伸的屋exception的階層了。
有兩個新的有效的(語意上的)形式如今能夠用來看成引起exception的敘述:
raise Class, instanceraise instance
在第一個形式裏面, instance 必須是 Class 這個類別或其子類別的一個instance。第二種形式實際上是底下這種形式的一個簡化:
raise instance.__class__, instance
因此如今在except的語句裏面就能夠使用字符串對象或是類別均可以了。一個在exception子句裏的類別能夠接受一個是該類別的exception,或者是該類別之子類別的exception。(相反就不能夠了,一個except子句裏若是用的是子類別,就不能接受一個基礎類別的exception。)例如,下面的程序代碼就會依序的印出B, C, D來:
class B:
passclass C(B):
passclass D(C):
passfor c in [B, C, D]:
try:
raise c()
except D:
print "D"
except C:
print "C"
except B:
print "B"
值得注意的是,若是上面的例子裏的except子句次序都掉轉的話(也就是 "except B" 是第一個),這樣子印出來的就是B, B, B,也就是隻有第一個能夠接受的except子句被執行而已。
當一個沒有被處理到的exception是一個類別時,所印出來的錯誤信息會包含其類別的名稱,而後是(:),而後是這個instance用內建的 str() 函式轉換成的字符串。
Footnotes... 一個namespace裏面。 9.1
除了一件事以外。module對象有一個祕密的attribute叫作 __dict__ ,這個attribute會傳回這個module的namespace所對應的dictionary。 __dict__ 這個名字就是一個attribute,但卻不是一個global的名稱。很明顯的,使用這個attribite將會破壞這個namespace命名的抽象性(abstraction),因此應該只限制於像是檢驗屍體同樣的除錯器使用。
11 如今呢
但願讀了這個教學文件已經增強了你對於使用Python的興趣。那麼,如今你應該要作什麼呢?
你應該要讀,或者至少應該翻閱過連接庫參考手冊,這會給你一個完整(雖然是簡潔)的有關型態,函式,模塊等的參考數據。並且當你在寫Python程序的時候,會節省你不少的時間。標準Python發佈的版本(distribution)裏面,都包含了 許多的 C以及Python的程序代碼。有用來讀Unix信箱(mailbox)的module,從HTTP讀取文件的module,以及分析檔案列選項(command-line options),寫CGI程序,壓縮數據以及許多其它的module。若是你瀏覽過這個連接庫參考手冊的話,你至少會有一個初步的印象到底有哪些連接庫能夠用。
最主要的Python網站是 http://www.python.org/ ,其中包含了許多的程序代碼,文件,以及許多其它有關Python的網頁的連結。這個網站在世界的不少地方也有mirror,例如日本,歐洲以及澳洲。你也許能夠找看看跟你的地區比較近的mirror站臺,也許速度會快一些。另一個非正式的網站是在 http://starship.python.net/ ,裏面有一大堆的Python相關的我的網頁,有許多人也從那裏下載程序。
若是你有跟Python相關的問題,或是要報告問題的話,你能夠post到 comp.lang.python 這個新聞羣組,或者直接把你的問題送到 python-list@python.org 這個mailing-list。這個新聞羣組及mailing list是相連起來的,因此你送到其中一個都會在兩個都看獲得。它們大概一天都有約120個新的 postings,其中包括問(及回答)問題,建議新功能,以及宣佈新的module等等。在你要post以前,你應該先看看常見問題(Frequently Asked Questions (也叫作FAQ)),其網頁在 http://www.python.org/doc/FAQ.html ,或者查看你的Python source distribution裏面的 Misc/ 目錄。Mailing list以前的存檔也都在 http://www.python.org/pipermail/ 。可是FAQ裏面已經包含了許多的一再被問到的問題,因此頗有可能已經有你的問題的答案。
12 A. 交互式輸入編輯及代換過去的內容
有些的Python的直譯器版本有支持目前所在行的編輯,以及過去輸入內容的代換,這跟在Korn Shell以及GNU Bash shell裏面的一些功能有點像。這個功能是用 GNU Readline 的連接庫作出來的,提供了相似Emacs以及相似vi的編輯功能。這個連接庫自己有本身的參考文件,因此我在此再也不重複,我只是簡單的介紹基本的功能。這裏所介紹的互動模式輸入編輯及代換過去內容的功能一般在Unix以及CygWin的直譯器版本均可以見獲得。
本章 沒有 包含在Mark Hammond的PythonWin package裏的編輯功能,也沒有包含在標準Python distribution裏面的Tk之類的環境,或是IDLE的編輯功能。在DOS以及NT及其它相似的環境下,也有命令列過去內容記憶的功能,可是也不在本文的內容之中。
12.1 A.1 整行編輯
若是有支持整行編輯的話,當直譯器印出primary prompt或是secondary prompt的時候,整行編輯的功能就會被啓動起來。你能夠用通常的Emacs控制字符來編輯目前所在的行。其中常見的有這些: C-A (Control-A)會移動cursor到目前行的最開頭的地方, C-E 會移動到最尾端, C-B 會往左移動一個位置, C-F 會往右邊一個位置。Backspace鍵會消去目前cursor所在處左邊的一個字符, C-D 會消去右邊的一個字符, C-K 會殺掉 (消去)本行在光標以後的全部字符, C-Y 會把剛剛所殺掉的字符串再次貼回來, C-underscore 會取消剛纔所作的最後一個動做,這個功能也能夠重複屢次的使用。
12.2 A.2 代換過去的內容
要取代過去的內容其方法以下:全部的非空白行都會被儲存在一個儲存過去內容的緩衝區(history buffer)裏面,當prompt出現的時候,你所在的位置就是在這個buffer的最底下,使用 C-P 會使得在buffer裏面往上移動一行, C-N 會往下移動一行。任何在buffer裏面的行均可以被編輯,如果在prompt以前出現星號的話就表明這個行已經被修改過了。當你按下 Return 鍵的時候就是把目前這一行送給直譯器了。 C-R 會開始一個逐漸往上的搜尋,按下 C-S 會開始往前的搜尋。
12.3 A.3 鍵盤連結
鍵盤的連結以及一些其它有關Readline library的參數均可以被修改,其方法是在一個叫作 ~/.inputrc 的初始化檔案中打入一些指令。鍵盤的連結有如下的幾種形式:
key-name: function-name
或是
"string": function-name
你也能夠設定一些選項:
set option-name value
請看下面的例子:
# I prefer vi-style editing:
set editing-mode vi
# Edit using a single line:
set horizontal-scroll-mode On
# Rebind some keys:
Meta-h: backward-kill-word
"\C-u": universal-argument
"\C-x\C-r": re-read-init-file
注意的是在Python裏面預設的 Tab 鍵所連結的是輸入一個 Tab 字符,而非Readline 連接庫裏面預設的文件名自動完成的功能。若是你堅持的話,你也能夠這樣子設定來蓋過Python的預設:
Tab: complete
這應該在你的 ~/.inputrc 裏面。(固然若是你這樣作的話,你在縮排連續行的時候就費力一些了。)
自動完成變量及module的名稱的功能是能夠自由選擇要或不要的。若是你要在互動模式下啓動這一個功能的話,能夠在啓動文件裏面加入底下的指令: A.1
import rlcompleter, readline
readline.parse_and_bind('tab: complete')
這樣作的話會使TAB 鍵連結到完成的功能,因此按下TAB鍵兩次就會建議一些完整的名稱。其所搜尋的是Python的敘述名稱,目前的local變量,以及可用的module名稱。對於像 string.a 這樣帶有點的expression,這項功能會先是着先evaluate到最後一個點的意思,而後再從所得的對象中建議可用的變量名稱。注意的是,若是這個對象含有的 __getattr__() 這個method是這個expression的一部份的話,就會執行一些特定的程序代碼。
12.4 A.4 評註
這個功能跟其它以前的直譯器版本比起來是很大的一個進步,可是咱們還有不少但願有的功能。若是在連續行的時候能夠建議適當的縮排距離(分析器(parser)應該知道什麼時候須要縮排)。自動完成的功能應該也可以使用直譯器的symbol table。應該也要有一個指令能夠檢查(甚至是建議)什麼時候應該要有結束的括號,括號等等。
腳註... 底下的指令: A.1
當你啓動一個Python的交互式直譯器時,Python會自動執行在 $PYTHONSTARTUP 這個系統環境變量所表明的檔案裏面的指令。
___html