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 + yx * ys[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 的實作,它本身也必須包含三個參數,分別是 argsubdirectorynames。這裡的參數 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) 來處理檔案的讀寫功能,語法及概念上,仍與 CPerl 語言裡有相似之處,我們可以直接觀察下列的範例:

>>> 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)。它共支援三種位移模式:即「絕對位置」、「相對位置」、「檔尾相對位置」,分別以 012 來表示,不過位移模式的預設值是 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 inputstandard 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