Python 語法隨手記錄 – 2

書名、原作者、原譯者資訊:

Python 教學文件

作者:
Guido van Rossum
Fred L. Drake, Jr., editor
譯者:
周譯樂


///// 功能式程式設計工具

有三個與 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

///// 傳回整個列 (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]

///// 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 的例子。

///// 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 來做。

///// 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

///// 條件(續)

在之前所談到的 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 的程式設計師也許會針對此點抱怨,但是這樣的好處是可以避免一些常見的把設定( = )及等於( == )弄混淆的情形

///// 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 的,其餘按此類推。

///// 模組

如果你離開 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

///// 模組(續)

一個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所有的定義的名稱,除了以底線 ( _ ) 開頭的之外。

///// 尋找模組的路徑

當你import一個叫做 spam 的module時,直譯器會先在目前的目錄尋找一個叫做 spam.py 的檔案,如果沒找到,會再依據定義在 $PYTHONPATH (一個環境變數)裡面的所有路徑來找。 $PYTHONPATH 的語法與設定方法與 $PATH 是一樣的,也就是一連串的目錄路徑的名稱。如果你沒有設定 $PYTHONPATH ,或是在這些目錄當中也找不到的話,直譯器會繼續在一個安裝時預設的目錄來找,在Unix的機器上,通常這個目錄是 .:/usr/local/lib/python 。

事實上,module的搜尋路徑是依照存在 sys.path 這一個變數中的一個許多路徑名稱組成的list。這個變數當在Python直譯器啟動時,會從輸入的script(或目前的目錄)、 $PYTHONPATH 、以及安裝時設定的預設目錄來讀取所有的目錄。如果你知道自己在做什麼的話,你可以修改這個變數來改變直譯器尋找module的路徑。請參閱之後的標準模組(standard module)一段。

///// “編譯過的”( “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 )。

///// 標準模組

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’)

///// 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']

///// 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裡面的類別、成員或函式。

///// 從一個 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的名字會和其他的名稱衝突,否則這是常被推薦使用的形式。

///// 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 這個敘述。

///// 花俏的輸出格式化

到現在為止我們談到了兩種寫入值的方式:用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 s
The 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。

///// 讀寫檔案

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程式庫而有不同的。)

///// 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物件的說明。

///// 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的過程。

你喜歡這篇文章嗎? 馬上分享它:

相關文章:

  1. Python 語法隨手記錄 – 1
This entry was posted in Python, 隨記 and tagged , . Bookmark the permalink.

Leave a Reply