Python
之旅 (四)文
/馬兒 (marr@linux.org.tw)認識函式與模組之後,
Python 的學習之路顯得寬廣許多,本期的重點將從字串延伸至檔案的處理上,包括檔案系統與資料 I/O 兩大部份,其學習經驗又與作業系統環境具有密切關係,最後並簡介例外處理的範例。幾個有用的函式
為了方便接續的內容,我們先來認識一個實用的模組,稱為
string,顧名思義,可用於協助處理字串物件。>>> import string
>>> date = "Fri May 18 CST 2001"
>>> piece1 = string.split(date)
>>> piece1
['Fri', 'May', '18', 'CST', '2001']
>>> time = "12:03:27"
>>> piece2 = string.split(time, ':')
>>> piece2
['12', '03', '27']
>>> string.digits
'0123456789'
由上述範例,可以得知模組
string 裡有個 split() 的物件方法,其工作原理是將一個字串變數值,依空白字元 (預設狀況) 為切割點,分解成數個小字串,形成一個字串串列傳回。如果切割條件不是空白字元時,在 split() 所接參數中予以指定,如 split(time, ':') 就是指定要以 ':' 字元為切割點。範例的最後一行,則是顯示模組 string 有個字串變數 digits,其內容設定為 '0123456789'。如果我們想把上述字串串列裡的「數字」,如
'18' 與 '2001',由字串型別轉換成數值型別,可以怎麼做呢? 下列是個方法。>>> def try_ai(s):
... if s[0] in string.digits:
... return string.atoi(s)
... else:
... return s
...
>>> import string
>>> date = "Fri May 18 CST 2001"
>>> piece = string.split(date)
>>> finish_ai = map(try_ai, piece)
>>> print finish_ai
['Fri', 'May', 18, 'CST', 2001]
>>>
首先,定義一個叫做
try_ai() 的函式,它在讀進字串後,會比對字串的第一個字元,如果第一個字元是屬於阿拉伯數字,那麼就會嘗試透過 string 模組的 atoi() 物件方法,將字串轉換成整數,最後傳回其整數型別資料。是的,你會發現它的演算規則有些天真,不過,我們暫時還不需要一個無懈可擊的轉換函式。我們載入模組
string 之後,利用內建函式 map() 將自製函式 try_ai 與字串串列 piece 連結起來。map() 接受兩個參數,一個是函式,一個是序列資料,它會依序讀取序列資料的元素內容,加工後傳回,如此一來,便能如願將 piece 裡的部份字串,轉換成數值型別,做法上顯得相當簡潔。接下來,我們就可以進一步稍微改良原本天真的
try_ai() 函式。>>> def try_ai(s):
... if ':' in s:
... ts = string.split(s, ':')
... return map(string.atoi,ts)
... if s[0] in string.digits:
... return string.atoi(s)
... else:
... return s
...
>>> import string
>>> date = "Fri May 18 12:03:27 CST 2001"
>>> piece = string.split(date)
>>> finish_ai = map(try_ai, piece)
>>> print finish_ai
['Fri', 'May', 18, [12, 3, 27], 'CST', 2001]
>>>
這個改良過的版本,可以進一步處理像
'12:03:27' 這樣的「數字」,否則原本更天真的版本會傳回 ValueError 的錯誤訊息。>>> piece = ['Fri', 'May', '18', '12:03:24', 'CST', '2001']
>>> def strp(x, y):
... return x + ' ' + y
...
>>> r = reduce(strp, piece)
>>> r
'Fri May 18 12:03:24 CST 2001'
>>>
上述程式片段,處理效果剛好與之前的程式相反,它會把字串串列重組成一個長字串。重點就是利用了內建函式
reduce(),其運作方式同樣要輸入一個函式名稱及一個序數資料,不過,目的是要把序數資料的元素「合併減少」成一個。reduce() 也可以應用在數值串列上,以下便是這樣的範例。>>> n = range(1, 11)
>>> def mult(x, y):
... return x * y
...
>>> f = reduce(mult, n)
>>> f
3628800
>>>
說穿了,它還是一個階乘的範例,每呼叫一次
mult() 函式,數值串列的個數會越來越少,最後傳回一個階乘結果,在此例中,即 10! 的答案。下列是一個簡化版本的閏年判斷程式,我們將引入另一個函式
filter()。>>> def leap(y):
... if (y%400) == 0:
... return 1
... elif (y%100) == 0:
... return 0
... elif (y%4) == 0:
... return 1
... return 0
...
>>> n = range(1900, 2001)
>>> leap_year = filter(leap, n)
>>> leap_year
[1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936, 1940, 1944, 1948, 1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000]
>>>
函式
filter() 同樣是接一個函式名稱與一個序數資料為參數,重點在於,在自製函式中,你必須把「想留下來的資料」,其函式傳回值設為 1 (代表 true),而把「不想留下來的資料」,其函式傳回值設為 0 (代表 false)。如此一來,filter() 函式在依序處理完序數資料後,還是會傳回一個序數資料,而且應該只留下你想要的資料。lambda
表示式lambda 表示式是個實用而重要的工具,其功能及本質還是函式,差別在於 lambda 沒有「明確的函式名稱」,而且通常只用於簡潔的函式敘述。
>>> n = range(1, 11)
>>> f = reduce(lambda x, y: x * y, n)
>>> f
3628800
>>>
又是熟悉的階乘函式,有趣的是,它的表示方式非常簡潔,乍看之下,初學者可能以為是天書了。
lambda 表示式的語法如下:lambda 參數串列: 表示式
例如 x + y、x * y、s[9] 都是表示式的例子。早期,lambda 表示式的概念是取自 Lisp 語言,使用上有其便利及優勢,不過,對初學者而言,使用 lambda 表示式通常還是得花時間熟悉,若是「畫虎不成反類犬」,搞到程式大亂就得不償失了。
讀取檔案系統
filesystem 資訊檔案系統的相關工作,大抵圍繞著檔案、目錄為主題,下列的基本功能應用場合極廣。
>>> import os
>>> os.getcwd()
'/home/marr'
>>> os.listdir(os.curdir)
['.bash_history', '.bash_logout', '.bash_profile', '.bashrc', '.muttrc', 'signature', '.ssh', '.viminfo', '.vimrc']
>>> os.chdir('/tmp')
>>> os.path.join('usr', 'local', 'bin')
'usr/local/bin'
>>> mount_point = '/mnt/cdrom'
>>> rpm_dir = 'Mandrake/RPMS'
>>> complete_dir = os.path.join(mount_point, rpm_dir)
>>> print complete_dir
'/mnt/cdrom/Mandrake/RPMS'
>>> os.path.split(complete_dir)
('/mnt/cdrom/Mandrake', 'RPMS')
>>> os.path.basename(os.path.join(complete_dir, 'some.rpm'))
'some.rpm'
>>> os.path.dirname(os.path.join(complete_dir, 'some.rpm'))
'/mnt/cdrom/Mandrake/RPMS'
>>> os.path.splitext(os.path.join(complete_dir, 'some.rpm'))
('/mnt/cdrom/Mandrake/RPMS/some', '.rpm')
>>> os.path.commonprefix(['/usr/bin', '/usr/src'])
'/usr/'
>>> os.path.expandvars('$HOME/temp')
'/home/marr/temp'
>>>
目錄、路徑的相關處理,與 os 模組有關,操作前要記得將之 import。
getcwd() 傳回現行工作目錄資訊。
利用 listdir() 可以將目錄內之檔案資訊列出,使用者必須提供「目錄字串」為輸入參數,如 os.curdir 指的便是「現行工作目錄」字串,而傳回值為一字串串列,即目錄下檔案名稱所集的串列。
利用 chdir() 可以進行目錄更換的動作,同樣是提供「目錄字串」為輸入參數。
此處顯示的是 Linux 環境下的執行結果,如果是 DOS/Windows環境,其執行結果會像是 usr\local\bin。
輸入一個檔案字串,os.path.split() 會傳回一個值組,其元素內容分別為目錄名稱與最後的檔案名稱。
輸入一個檔案字串,os.path.basename() 會傳回一個字串,為路徑資訊中最後的檔案名稱, os.path.dirname() 會傳回一個字串,為路徑資訊中前面的目錄名稱。
輸入一個檔案字串,os.pah.splitext() 可以傳回一組值組,其元素內容分別是檔案名稱的前部份,以及最後的延伸檔名。
輸入一組路徑字串串列,os.path.commonprefix() 可以協助找出最小的共同路徑值。
透過 os.path.expandvars() 可以應用類似 $HOME 的環境變數。系統檔案處理的必備工具
下列的常數與系統環境有關,使用上無須輸入值。
常數名稱 |
功能說明 |
傳回值範例 |
os.name |
傳回「作業系統環境名稱」字串 |
'nt', 'posix' |
sys.platform |
傳回「作業平台名稱」字串 |
'win32', 'linux-i386' |
os.environ |
傳回「系統環境變數」辭典集 |
{ 'HOME': '/home/marr', 'SHELL': '/bin/bash'} |
os.curdir |
代表「現行工作目錄」 |
'.' |
os.pardir |
代表「父層目錄」 |
'..' |
既然
os.environ 傳回的是辭典集,我們還可以透過 os.environ["PATH"] 方式來取出特定的環境變數值。另外像 sys.argv 是經常被利用到的變數,可以參考下列的例子:example$ cat prn_argv.py
#!/usr/bin/python
import sys
if len(sys.argv) < 2:
print "enter some arguments with this script."
sys.exit(0)
else:
print "there are %d arguments: " % (len(sys.argv),)
for n in range(len(sys.argv)):
print sys.argv[n],
example$ ./prn_argv.py
enter some arguments with this script.
example$ ./prn_argv.py argu1 argu2 argu3
there are 4 arguments:
./prn_argv.py argu1 argu2 argu3
下列的函式與檔案資訊有關,使用上必須輸入字串變數
(通常就是檔案名稱)。
函式名稱 |
函式功能 |
操作範例 |
os.path.exists() |
測試字串變數是否存在。 |
os.path.exists('/etc/passwd') |
os.path.isdir() |
測試字串變數是否為目錄。 |
os.path.isdir('/etc') |
os.path.isfile() |
測試字串變數是否為檔案。 |
os.path.isfile('/etc/X11/X') |
os.path.islink() |
測試字串變數是否為連結檔案。 |
os.path.islink('/etc/X11/X') |
os.path.samefile() |
測試兩個字串變數是否指向同一檔案。 |
os.path.samefile('/bin/sh', '/bin/bash') |
os.path.isabs() |
測試字串變數是否為絕對路徑。 |
os.path.isabs('/home/marr') |
os.path.getsize() |
傳回字串變數之檔案大小。 |
os.path.getsize('/etc/passwd') |
os.path.getatime() |
傳回字串變數之最後存取時間。 |
os.path.getatime('/etc/passwd') |
os.path.getmtime() |
傳回字串變數之最後修改時間。 |
os.path.getmtime('/etc/passwd') |
下列的函式與檔案處理有關,使用上必須輸入字串變數
(通常包含檔案名稱)。
函式名稱 |
函式功能 |
操作範例 |
os.mkdir() |
以指定模式建立一個新目錄。 |
os.mkdir("/tmp/beatles", 0700) |
os.rmdir() |
刪除指定目錄。 |
os.rmdir("/tmp/beatles") |
os.rename() |
將檔案或目錄名稱更改。 |
os.rename("/tmp/beatles", "/tmp/smiths") |
os.link() |
為檔案建立一個連結。 |
os.link("/tmp/old_file", "/tmp/ln_file") |
os.symlink() |
為檔案建立一個符號連結。 |
os.symlink("/tmp/old_file", "/tmp/sln_file") |
os.unlink() |
刪除檔案的連結。 |
os.unlink("/tmp/sln_file") |
os.stat() |
取得檔案完整相關資訊。 |
os.stat("/etc/X11/X") |
os.lstat() |
與 stat() 相同,但不傳回連結的檔案。 |
os.lstat("/etc/X11/X") |
傳統
Unix shell 處理檔案名稱時,可以應用特殊字元,諸如 *、?、[A-z] 等,想要取得這樣的功能便利,呼叫 glob 模組即可。>>> import glob
>>> glob.glob("*")
['README', 'all.tex', 'python1.html', 'python2.html', 'python3.txt', 'python4.tex']
>>> glob.glob("python?.html")
['python1.html', 'python2.html']
>>> glob.glob("[A-a]*")
['README', 'all.tex']
>>>
實務程式寫作上,可能經常遇到
recursive directory 的問題。函式 os.path.walk() 提供一個簡潔的方式來協助處理這類題目,它可以依序讀取 directory tree 底下的所有子目錄檔案,使用時必須接受三個參數,其應用格式如下:os.path.walk(directory, function, arg)
第一個參數
directory 是 tree 的起始點,第二個參數 function 是打算應用的函式名稱,第三個參數 arg 則是可以應用於 function 中的傳入值。比較複雜的是第二項參數 function 的實作,它本身也必須包含三個參數,分別是 arg、subdirectory、names。這裡的參數 arg 即是os.path.walk() 所傳來的參數值,參數 subdirectory 是正在處理當中的目錄名稱,而參數 names 則是 os.listdir(subdirectory) 的檔案名稱串列結果。下列是一個應用 os.path.walk() 的簡單範例。example$ cat walk_prn.py
#!/usr/bin/python
import os
def printFunction(arg, directory, names): print directory, '\n', names, len(names)
os.path.walk(os.path.getcwd(), printFunction, None)
檔案存取功能
Python 以「檔案物件」(file object) 來處理檔案的讀寫功能,語法及概念上,仍與 C、Perl 語言裡有相似之處,我們可以直接觀察下列的範例:
>>> fileobject = open("/etc/group", 'r')
>>> fileobject.tell()
0
>>> fileobject.readline()
'root:x:0:root\012'
>>> fileobject.tell()
14
>>> fileobject.readline()
'bin:x:1:root,bin,daemon\012'
>>> fileobject.read(6)
'daemon'
>>> fileobject.seek(-25, 2)
>>> fileobject.readlines()
['mysql:x:237:\012', 'marr:x:501:\012']
>>> fileobject.close()
>>>
檔案物件的建立 (開啟),是透過函式 open() 來完成,最後可以透過 close() 的物件方法來關閉。而開啟的方式,最簡單的便是上述的 read ('r') 方式,如果要開啟檔案供寫入資料,則是類似 open("myfile", 'w') 語法,代表要 write ('w') 到檔案裡頭。另外還有 append ('a') 的開啟方式,此一模式會將指標位置預設移到檔案末尾處,以便使用者新增資料。
檔案物件的資料存取,與「指標位置」(position) 密切相關,剛開始讀進一個新的檔案物件,指標位置被設定為 0,表示在最開頭處,隨著資料的讀取,指標位置就會跟著變動,我們可以不時透過物件方法 tell()來得知現行的指標位置。
物件方法 readline() 可以逐行讀進檔案物件的內容,較精確點的描述,是由現行指標位置讀至換行符號 '\n' 為止,傳回之資料型別為字串。
使用 readlines() 的話,可以由現行指標位置一次讀至 EOF,傳回之資料型別為字串串列。
物件方法 read() 可以依使用者的需求,讀進特定位元組的資料,如 read(6) 就是由指標位置開始,讀進六個字元,傳回之資料型別仍為字串。
透過物件方法 seek(),我們可以「隨心所欲」地調整指標位置,使用時,通常必須提供二個輸入值,分別是位移值 (offset) 與位移模式 (mode)。它共支援三種位移模式:即「絕對位置」、「相對位置」、「檔尾相對位置」,分別以 0、1、2 來表示,不過位移模式的預設值是 0。如 seek(14, 0) 是指移至指標位置為 14 的地方,seek(-5, 1) 是指由現行指標位置減少 5,而 seek(-25, 2) 是指由檔案末尾處,減少 25 個位移值。有關字串或串列資料寫入檔案的動作,可以透過物件方法
write() 或 writelines() 來完成,在此我們並不詳加介紹,讀者很容易在函式庫手冊中查到使用方法。螢幕之輸出入功能
raw_input() 與 input() 是兩個基本的內建函式,都可以用來協助處理螢幕輸出入的動作。
>>> x = raw_input("enter a string from standard input: ")
enter a string from standard input: my string
>>> x
'my string'
>>> x = int(raw_input("enter a number: "))
enter a number: 17
>>> x
17
>>> y = input("enter a data object: ")
enter a data object: 18
>>> y = input("enter a data object: ")
enter a data object: 'my data'
>>> y = input("enter a data object: ")
enter a data object: 3 + 6
>>> y
9
>>> input("enter a data object: ")
enter a data object: my string
Traceback (innermost last):
File "<stdin>", line 1, in ?
File "<string>", line 1
my string
^
SyntaxError: unexpected EOF while parsing
>>>
函式 raw_input() 可以接受一段字串作為「提示」,使用者在提示詞之後所輸入的資料會被當作字串處理。值得注意的是,輸入資料列最後的換行符號已「自動」被處理掉了。
如果有意將 raw_input() 得到的輸入值,其資料型別由字串改為整數,可以再套用函式 int()。
函式 input() 可以視作「更一般化」的標準輸出入工具,在上述的例子中,我們觀察到,可以當作輸入資料的類型有數值、字串、表示式等。
當函式 input() 要讀進一個字串時,必須明確以 'my string' 之類的表示方法輸入,不然會發生 SyntaxError 之類的錯誤。進一步來看,實際上,函式
raw_input() 與 input() 都是應用 standard input、standard output 之輸出入功能來實作,這部份的邏輯就和 C 程式語言的傳統相仿,如果想要低階地控制「標準輸入」(standard input)、「標準輸出」(standard output)、「標準錯誤」(standard error) 的動作,應該配合載入 sys 模組。>>> import sys
>>> type(sys.stdout)
<type 'file'>
>>> sys.stdout
<open file '<stdout>', mode 'w' at 80b38c8>
>>> sys.stdin
<open file '<stdin>', mode 'r' at 80b36c0>
>>> sys.stdout.write("Write to the standard output.\n")
Write to the standard output.
>>> s = sys.stdin.readline()
line data from standard input
>>> s
'line data from standard input\012'
>>> f = open("my_file.txt", 'w')
>>> sys.stdout = f
>>> sys.stdout.writelines(["first line.\n", "second line.\n"])
>>> print "a line from print statement."
>>> 5 + 12
>>> sys.stdout = sys.__stdout__
>>> f.close()
>>> 6 + 12
18
>>>
example$ cat my_file.txt
first line.
second line.
a line from print statement.
17
如果熟悉一般檔案的存取規則,那麼依此類推,sys.stdout 或 sys.stdin 的操作也就顯得容易理解,可以將之視為系統預設開啟的檔案物件,其中 <stdout> 之開啟模式為 'w',<stdin> 之開啟模式為 'r'。
這裡示範的是「標準輸出轉向」(redirection) 動作,先開啟一個檔案物件 f,然後以 sys.stdout = f 敘述式,指定標準輸出的內容將轉向至檔案物件 f。其後的 writelines()、print 等相關使用到標準輸出的動作,其資料輸出都會被寫入檔案物件 f 裡頭。
記得將標準輸出的「原狀」恢復,透過 sys.stdout = sys.__stdout__ 敘述即可完成。
再次測試標準輸出的結果,可以發現資料輸出又回到螢幕上。另外觀察檔案 my_file.txt 的內容,可以發現之前因轉向而寫入的資料。物件之輸出入功能
讀者已經了解一般檔案的存取方式,也見識了標準輸出入的處理技巧,而
Python 另一項實用的技巧,就在於物件資料的快速存取能力,這點一般是透過 cPickle 模組的支援。早期 Python 程式裡就存在有一個 pickle 模組,因為執行效率的考量,多數都改用 cPickle 這個由 C 程式語言所改寫的模組,除非程計員有意從 pickle 模組繼承出新的類別。而 cPickle 應該都可以在新版的 Python 程式套件中找到。程式執行過程中,或是執行結果所產生的資料,經常會被下次的程式執行所再利用。如果以純文字檔型式將資料予以儲存,或許會有便於查看的好處,但是重複應用上的效率總是不夠理想,如果能夠直接將物件以檔案型式儲存,然後又快速讀出恢復為原有物件,應用上有其優勢,而
Python 將這類動作稱為「序列化」(serializing 或 pickling) 及「反序列化」(unpickling)。#!/usr/bin/python
import cPickle
name_list = ['Morrissey', 'Johnny Marr', 'Andy Rourke', 'Mike Joyce']
output = open("name_file", 'w')
cPickle.dump(name_list, output)
output.close()
input = open("name_file", 'r')
loaded_list = cPickle.load(input)
input.close()
print loaded_list
cPickle 模組的使用,最簡易的方式就是利用 dump() 函式來將物件序列化,並利用 load() 函式來反序列化,dump() 函式須指定欲儲存之物件及檔案物件名稱,load() 函式則須給定檔案物件名稱。下列則是另一個更接近實務的例子:
#!/usr/bin/python
import cPickle
name_list = ['John Lennon', 'Paul McCartney', 'George Harrison', 'Ringo Starr']
birth_yr = [1940, 1942, 1943, 1940]
play_list = ['guitar', 'bass', 'guitar', 'drum']
save_data = (name_list, birth_yr, play_list)
output = open("save_file", 'w')
cPickle.dump(save_data, output)
output.close()
input = open('save_file', 'r')
(name, birth, play) = cPickle.load(input)
input.close()
print name
print birth
print play
延續上述的例子,我們可以使用
shelve 模組,改寫出一個足夠廣泛應用的範例。#!/usr/bin/python
import shelve
name_list = ['John Lennon', 'Paul McCartney', 'George Harrison', 'Ringo Starr']
birth_yr = [1940, 1942, 1943, 1940]
play_list = ['guitar', 'bass', 'guitar', 'drum']
shv = shelve.open("save_shv")shv['name'] = name_list
shv['birth'] = birth_yr
shv['play'] = play_list
shv.close()
shv = shelve.open("save_shv")
key_list = shv.keys()
for key in key_list:
print shv[key]
shv.close()
上述的範例程式,在技術概念上是相當深入了,不過乍看之下還算易懂,所以一併討論。如果有人對「序列化」的資料還是覺得不夠方便,譬如說,當儲存在檔案裡的資料量過於龐雜,想要找回所需要的物件可能就增加了難度,此時利用「索引」來增加可讀性,便是值得考慮的做法。
shelve 模組提供類似 cPickle 模組的功能,但是其傳回值的儲存方式類似辭典集,稱為書架 (shelf) 物件型別,所以事後可以透過鍵值來快速取回原有的物件內容。例外處理
(exception)和其他物件導向式語言類似,
Python 語言也是透過 try ... except 這樣的語法結構來處理例外事件。相較來看,Java 和 C++ 的語法結構是以 try ... catch 來處理例外,而使用 throw 來產生例外。例外事件也是物件,在 Python 語言不但隨處易見,而且有其物件類別體系,我們早已經在先前的內容中,不只一次看到「例外」物件,介紹過的都是伴隨內建函式操作時的場合。●
TABLE_BEGIN●表
: 常見之內建例外
例外類別 |
說明 |
Exception |
所有例外 |
StandardError |
標準例外 |
ArithmeticError |
數學例外 |
FloatingPointError |
浮點運算錯誤 |
OverflowError |
運算溢位錯誤 |
ZeroDivisionError |
除數為零錯誤 |
AttributeError |
屬性名稱不存在 |
AssertionError |
由 assert 敘述所拋出的例外 |
EnvironmentError |
由 Python 以外程式所發生的錯誤 |
IOError |
I/O 錯誤或檔案存取錯誤 |
OSError |
作業系統錯誤 |
EOFError |
到達檔案結尾所拋出的例外 |
ImportError |
敘述所導致的匯入錯誤 |
KeyboardInterrupt |
由中斷鍵 (一般是 Ctrl + C) 所拋出的例外 |
LookupError |
索引或鍵值錯誤 |
IndexError |
超出序數的位移範圍 |
KeyError |
不存在的索引鍵值 |
MemoryError |
記憶體不足的錯誤 |
NameError |
區域名稱或全域名稱搜尋錯誤 |
RuntimeError |
一般性例外擷取錯誤 |
NotImplementedError |
尚未實作完成之錯誤 |
SyntaxError |
語法解析錯誤 |
SystemError |
Python 直譯器的小錯誤 |
SystemExit |
由 sys.exit() 所拋出的例外 |
TypeError |
型別傳遞錯誤 |
ValueError |
型別無效 |
●
TABLE_END●例外本身呈現階層式架構,只要是分在同一組的,都可以直接用該組最上層的例外名稱來代表,例如
LookupError 可用來代表擷取 IndexError 與 KeyError 兩種例外,而 StandardError 則可以用來代表幾乎所有的例外了。例外事件之所以「等同於」錯誤,主要原因之一,是我們學習
Python 至此,並未主動對於例外採取任何處理措施。除了程式邏輯錯誤所導致的錯誤外,許多時候,例外是容易事先被預期的,譬如說,想要開啟某個原本以為已存在的檔案,或是連線讀取某個網路上的資料庫,經常會因為時地改變,而導致無法順利執行完成,這類非關鍵性的例外,程式人員可以考慮妥善予以處理。try:
body
except exceptionType1, var1:
exceptionCode1
except exceptionType2, var2:
exceptionCode2
...
...
except:
defaultExceptionCode
else:
elseBody
上述的語法結構,就是
Python 例外處理的流程示意,我們配合下列的例子來實際體會。example$ cat exception.py
#!/usr/bin/python
class EmptyFileError(Exception):
pass
FileNames = ['my_file', 'non_existent', 'empty_file']
for file in FileNames:
try:
fo = open(file, 'r')
line = fo.readline()
if line == "":
fo.close()
raise EmptyFileError("%s: is empty" % (file))
except IOError, error:
print "%s: could not be opened: %s" % (file, error[1])
except EmptyFileError, error:
print error[0]
else:
fo.seek(0)
lines = fo.readlines()
print "%s: %d lines" % (file, len(lines))
fo.close()
example$ ./exception.py
my_file: 2 lines
non_existent: could not be opened: No such file or directory
empty_file: is empty
Python 透過 raise 語法來產生例外物件,其中的 error 變數儲存著例外發生後可以顯示的訊息內容。
相關說明
[1] 來點不一樣的吧,歡迎訪閱 Instant Python 網頁,網址 http://www.hetland.org/python/instant-python.php
[2] 想讓 Python 執行更有效率嗎? 試試Python Performance Tips 網頁,網址http://musi-cal.mojam.com/~skip/python/fastpython.html
[3] Python for Lisp Programmers 網頁,網址 http://www.norvig.com/python-lisp.html
[4] A Comparison of Python and LISP 網頁,網址http://www.strout.net/python/pythonvslisp.html
[5] A Python/Perl phrasebook 網頁,網址http://starship.python.net/~da/jak/cookbook.html
[6] Major Python changes 網頁,提供Python 改版增修重點說明,網址http://shell.rmi.net/~lutz/errata-python-changes.html
[7] Python Language Mapping 網頁,網址http://www.informatik.hu-berlin.de/~loewis/python/pymap.htm