Python
之旅 (三)文
/馬兒 (marr@linux.org.tw)上期內容當中,讀者已經瀏覽過
Python 語言裡的一些基本資料型別元件,我們將接續地看到更多實用而有趣的範例。本期的重點擺在字串、函式、模組、系統資訊的處理上,這些學習經驗仍然是 Python 程式設計的重要基礎。連續數值的指定
: 內建函式 range()range 是一個便利的內建函式,可以傳回一組整數值的串列。
>>> range(7)
[0, 1, 2, 3, 4, 5, 6]
>>> (mon, tue, wed, thu, fri, sat, sun) = range(7)
>>> mon
0
>>> sun
6
>>> range(-5, 5)
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
>>> range(0, 10, 3)
[0, 3, 6, 9]
range 的預設值由 0 為起始。
將一個值組與 range 函式所產生的串列進行對映,所以指定結果是 mon 為 0,而 sun 為 6。
可以為 range 函式指定起始值與終止值,如 (-5, 5) 是指 -5 到 4 所形成的整數值串列。
先前的例子都省略了間隔值的設定,而使用其預設間隔值 1,在此我們可以加入間隔值的設定,如例中的最後一個參數 3。下列的簡單範例,可以將一組資料進行編號。
>>> weekday = ('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun')
>>> for day in range(len(weekday)):
... print day, weekday[day]
...
0 mon
1 tue
2 wed
3 thu
4 fri
5 sat
6 sun
實務程式寫作上,
range函式 與 for 迴圈經常搭配。還記得上期內容裡的 ascii.py 範例嗎? 我們可以使用 for 迴圈與 range 函式加以改寫,來個溫故知新。example$ cat ascii.py
#!/usr/bin/python
for i in range(256) :
print chr(i),
if i != 0 and i % 16 == 0 :
相較之下,程式碼本身顯得簡化了,但語法型態與
while 不同,讀者應用時大抵挑自己習慣者即可。資源與效率的抵換
除了
range() 之外,還有個 xrange() 內建函式,不但名稱相近,功能可說完全一致,其傳回值最後是相同的。差別在於 range() 會以串列資料型別來儲存所產生的傳回值,實際在記憶體裡佔有完整空間,而 xrange() 則是傳回一個 XRangeType 的物件,並不會在記憶體裡佔有太多儲存空間,而是等到實際存取資料時才繼續算出其值。所以在大筆資料的實務應用上,使用 range() 會佔用較多記憶體,但效率快些,而 xrange() 則節省記憶體空間,而犧牲了效率。請讀者善用 Python 的設計特性,依場合需要自行選用合適的函式。>>> range(1000000)
>>> xrange(1000000)
如果你的電腦系統資源豐富,上述兩個例子「看不出差別」,可以繼續嘗試更大的輸入值來測試。
函式
(function) 的使用函式功能在程式語言裡是必備的,不管是函式或模組的寫作,都要求程式員盡可能做到易讀易懂、功能獨立、可重複使用的程式片段,「避免重造輪子」,這樣的設計理念,在
Python 語言裡便極受重視。相信讀者至少已經熟悉一種其他程式語言的函式功能,我們可以直接觀察下列的範例:>>> def fact(n):
... """Return the factorial of the given number."""
... r = 1
... while n > 0:
... r = r * n
... n = n - 1
... return r
...
>>> fact.__doc__
'Return the factorial of the given number.'
>>> fact(5)
120
>>>
函式還是一個物件,其指定方式,是以保留字
def (即 define 之意) 為首,餘下的函式內容,同樣要按照「Python 的程式碼縮排原則」,否則會產生「IndentationError」的錯誤訊息。慣例上,函式的第二行會是一段「三引號註釋字串」,即 """ 所括夾的文字,稱為「文件字串」(documentation strings),我們可以透過如 fact.__doc__ 的物件方法,再把文件字串內容顯示出來,這樣的實務慣例,通常在大型而正式的 Python 程式開發專案裡顯得有用。fact() 是一個階乘函式範例,請注意到最後一行的保留字 return,如果少了 return 敘述 [1],則預設會以 None 值傳回。以範例函式 fact() 來看,n 是函式的參數 (argument),其傳回值 r 是 n 的階乘結果。
保留字、識別字、內建函式名稱
程式語言裡的保留字
(reserved words) 與識別字 (identifiers),是用來識別變數、函式、類別、模組、物件的名稱,我們先前見過如 print、is、while、for、def、return 等,都是 Python 保留字的範例。另外,以 _、__ 為開頭的識別字名稱,許多是 Python 的保留識別字,如 __doc__、__name__、__builtins__ 等,它們通常有特殊意義,日後我們會看到越來越多這樣的例子。至於內建函式名稱,讀者已經見過的,如 chr()、id()、len()、max()、range() 等。值得注意的是,上述識別字相關名稱,都是大小寫有別的 (case sensitive)。對於打算長期與 Python 廝混的朋友而言,應該有必要手邊準備一份保留字相關資訊。Python 識別字指定規則:
http://www.python.org/doc/2.0/ref/identifiers.html
Python 所有保留字資訊:
http://www.python.org/doc/2.0/ref/keywords.html
>>> def fact(n=10):
... """Return the factorial of the given number, with the defult input value."""
... r = 1
... while n > 0:
... r = r * n
... n = n - 1
... return r
...
>>> fact(5)
120
>>> fact()
3628800
>>>
上述程式片段示範了函式預設輸入值的設定方式,試試以
fact(5) 呼叫範例函式,會得到傳回值 120,而以 fact() 呼叫,則會傳回以 10 為預設輸入值的 3628800。>>> def power(x, y=2):
... r = 1
... while y > 0:
... r = r * x
... y = y - 1
... return r
...
>>> power(2, 4)
16
>>> power(3)
9
>>> power()
Traceback (innermost list):
File "<stdin>", line 1, in ?
TypeError: not enough arguments: expected 1, got 0
>>> power(2, 4, 3)
Traceback (innermost list):
File "<stdin>", line 1, in ?
TypeError: too many arguments: expected 2, got 3
>>>
一個函式可以設定多個輸入值,上例
power() 函式可以接受兩個輸入值,但至少需要 (expect) 一個輸入值,因為參數 y 有個預設值 2,輸入參數時可以省略之。以 power(2, 4) 呼叫時,會成功傳回 16,以 power(3) 呼叫時,會自動以 power(3, 2) 為輸入值,傳回 9。如果以 power() 呼叫,則會產生 TypeError 的錯誤訊息,它會明確告知「參數不足」,你必須至少輸入幾個參數,如果是以 power(2, 4, 3) 來呼叫,則會得到「參數過多」的 TypeError 訊息。>>> power(y=4, x=2)
16
>>>
另一個有用的應用技巧,稱為「關鍵字派定法」
(keyword passing),是以類似 power(x=2, y=4) 方式來呼叫,明確地將參數值配合變數名稱通知函式,甚至也可以用 power(y=4, x=2) 方式來呼叫,如此一來,兩者的呼叫結果是完全相同,意即其參數值順序可以任意改變。我們接著來看一些任意個輸入值的例子,相信其實用價值極高。
>>> def maximum(*numbers):
... if len(numbers) == 0:
... return(None)
... else:
... max = numbers[0]
... for n in numbers[1:]:
... if n > max:
... max = n
... return max
...
>>> maximum(3, 8, 5)
8
>>> maximum(2, -5, 9, 1, 7)
9
>>>
函式
maximum() 的用意很明顯,我們可以輸入任意個數的數值,而它最後會傳回最大值。例如 maximum(3, 8, 5) 會傳回 8,而 maximum(2, -5, 9, 1, 7) 會傳回 9。值得注意的地方,就是其處理不定參數個數的技巧,參數指定以 *numbers 方式代表,而 numbers 本身是一個值組 (即 tuple,而非 list)。函式的演算規則倒簡單,先把第一個數 numbers[0] 設為最大值,再將剩下的數 numbers[1:] 所形成之值組,丟進 for 迴圈,以「暴力法」比大小。下列的例子,將參數設定的可能狀況大抵做了整合介紹,我們可以一窺函式參數派定的相關細節,值得讀者反覆測試觀察。
>>> def args_func(a, b, c=9, *other1, **other2):
... return [a, b, c, other1, other2.items()]
...
>>> args_func(1, 2, 3)
[1, 2, 3, (), []]
>>> args_func(b=1, a=2)
[2, 1, 9, (), []]
>>> args_func(1, 2, 3, 4, 5, 6, 7, 8, 9)
[1, 2, 3, (4, 5, 6, 7, 8, 9), []]
>>> args_func(1, c=3, b=2, d=4, e=5)
[1, 2, 3, (), [('d', 4), ('e', 5)]]
>>> args_func(d=4, e=5)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: not enough arguments; expected 2, got 0
範例函式 args_func() 可以輸入三個 (以上的) 參數,參數名稱分別是 a、b、c,其中 c 有預設值為 9,a 與 b 是必要的輸入參數。而 *other1 用以指定 a、b、c 關鍵字之外的參數值 (不定個數),**other2 則是用以指定 a、b、c 關鍵字之外的派定值 (同樣是不定個數)。
函式 args_func() 非常簡潔,直接把所有的輸入參數值以串列資料型別傳回。其中的 other1 屬於值組資料型別,而 other2 則是辭典集 (dictionary) 資料型別。別慌,稍後會為讀者解說辭典集的相關細節。
給定三個參數值,它們會分別成為函式 args_func() 的 a、b、c 的設定值,此時 other1 與 other2 都是空空如也。
使用關鍵字派定法來指定 a 與 b 的參數值,而使用 c 的預設參數值。
給定了九個參數值,前三個依序成為 a、b、c 的參數值,後六個數值則成為值組 other1 的元素內容。
給定了五個參數值,第一個成為 a 的參數值,b 與 c 以關鍵字派定法來指定,而最後的 d 與 e 則成為不定個數的關鍵字派定值,它們被辭典集 other2 所收留了。內建資料型別
dictionary 的使用辭典集是
Python 裡的內建映射物件 (mapping object),也就是由一個物件集合來作為另一個物件集合的鍵值索引。映射物件和之前談過的序數資料相較,在概念上有擴充、補強的涵意,善用映射物件的特性,可以協助我們將看似複雜的問題,以相當直覺的方式解決。兩者具有若干不同之處,例如序數以索引運算作為其取值方式,映射物件的元素成份來源很有彈性,映射物件是不做排序的,而且映射物件是可變物件。●
TABLE_BEGIN●表
: 序數型別與映射型別的比較
序數型別 |
映射型別 |
以索引運算作為其取值方式。 |
以 keys()、values()、items() 等內建函式來取值。 |
以整數作為其索引 (index)。 |
以不可變物件作為其鍵 (key)。 |
元素具排序效果。 |
元素儲存時不做特定排序。 |
不一定都屬於可變物件。 |
辭典集是可變物件。 |
●
TABLE_END●在功能上,辭典集的實作相當於資料結構裡的雜湊表
(hash table) 或是相關陣列 (associative array),所以你會看到「鍵-值」(key-value pairs)的表示法。通常我們會以字串來做為辭典集的 key,因為有意義的字串可以帶來「望文生義」的效果,不過,一定要以不可變物件來做辭典集的 key,而 value 的部份就全無限制了。>>> x = []
>>> y = {}
>>> x[0] = 'Beatles'
Traceback (innermost last):
File "<stdin>", line 1, in ?
IndexError: list assignment index out of range
>>> y[0] = 'John Lennon'
>>> y[1] = 'Paul McCartney'
>>> y[2] = 'George Harrison'
>>> y[3] = 'Ringo Starr'
>>> y[0] + " and Yoko Ono"
'John Lennon and Yoko Ono'
>>> y
{3: 'Ringo Starr', 2: 'George Harrison', 1: 'Paul McCartney', 0: 'John Lennon'}
>>> y.keys()
[3, 2, 1, 0]
>>> y.values()
['Ringo Starr', 'George Harrison', 'Paul McCartney', 'John Lennon']
>>> y.items()
[(3, 'Ringo Starr'), (2, 'George Harrison'), (1, 'Paul McCartney'), (0, 'John Lennon')]
>>>
分別起始建立一個空串列 x 與一個空辭典集 y。由於無法指定不存在的索引值給串列,所以 x[0] 的指定動作宣告失敗,Python 回報 IndexError 錯誤訊息。
對於辭典集而言,並無上述的困擾,修改或增添元素資料均無限制。此例中,我們分別指定了 y[0]、y[1]、y[2]、y[3] 四筆元素資料。
可以針對元素進行各式運算,例如我們拿字串 y[0] 與另一字串合併顯示。
注意到辭典集的儲存,並沒有特定的順序方式,如果想要依特定的排序方法處理資料,可以另尋變通方法。
示範辭典集最常見的內建函式,即 keys()、values()、items(),它們都是傳回串列物件。>>> Beatles = {'leader':'John','bass':'Paul','guitar':'George','drum':'Pete'}
>>> Hurricanes = {'drum':'Ringo'}
>>> Beatles.update(Hurricanes)
>>> Beatles
{'drum': 'Ringo', 'leader': 'John', 'bass': 'Paul', 'guitar': 'George'}
>>> Beatles.get('leader', "Not available")
'John'
>>> Beatles.get('manager', "Not available")
'Not available'
>>>
我們為一個叫做 Beatles 的樂團建立辭典集,指定其鍵值及元素值,共計四項元素。
另一個叫做 Hurricanes 的樂團,其辭典集元素,只有鼓手的設定資料。
透過 update() 物件方法,我們更新了 Beatles 鼓手的設定資料。
get() 物件方法是詢問 Beatles 裡是否有 leader 此一鍵值,若存在則傳回其對應之元素值,否則會傳回後頭的字串資料。相信讀者至此已對辭典集有了基礎認識,其他辭典集相關的物件方法及運算元,請參考表格內容說明。值得一提的是,
Python 裡的辭典集實作得相當有效率,就算和串列型別相較,你也應該會滿意於它的便利與速度,適當的話,可以考慮多加使用。●
TABLE_BEGIN●表
: 辭典集的方法和操作
項目 |
說明 |
len(dict) |
傳回辭典集 dict 裡的元素個數。 |
dict[k] |
傳回鍵值 k 的元素內容。 |
dict[k]=v |
將 dict[k] 的內容設定為 v。 |
del dict[k] |
將 dict[k] 元素項目移除。 |
dict.clear() |
將辭典集 dict 所有元素項目全部移除。 |
dict.copy() |
將辭典集 dict 整個複製。 |
dict.has_key[k] |
如果辭典集 dict 含有鍵值 k 的話,則傳回 1,否則傳回 0。 |
dict.items() |
以值組 (key, value) 的串列型別傳回辭典集中所有元素。 |
dict.keys() |
傳回辭典集 dict 的所有鍵值。 |
dict.values() |
傳回辭典集 dict 的所有元素值。 |
dict.update(other) |
將辭典集 other 所有物件更新至辭典集 dict 當中。 |
dict.get(k [, other]) |
如果 dict[k] 存在則傳回 dict[k],否則傳回 other。 |
●
TABLE_END●模組
(module) 的使用簡單地說,模組代表著某個檔案名稱,該檔案名稱必須以
.py 延伸檔名作結,我們可以到 Python 的安裝目錄一窺究竟,以 Linux Mandrake 7.2 為例,其安裝目錄為 /usr/lib/python1.5。目錄裡包含類似 string.py、os.py、find.py 的檔案,我們可以透過 import string, os, find 之類的程式語法呼叫這些模組內容。也可以直接閱讀這些 .py 檔案的程式碼,相信部份檔案的內容,對你而言已不再全是天書。別誤會了,模組本身也可以是編譯過的檔案,如
.pyc 或 .pyo 檔案即屬此類。在系統目錄裡存在這類檔案,通常可以達到執行加速的效果。實際動手撰寫自己的模組之前,我們得先認識內建函式
dir() 的功能,它可以將許多物件內部的資訊顯示出來。>>> dir()
['__builtins__', '__doc__', '__name__']
>>> dir(__doc__)
[]
>>> print __doc__
None
>>> print __name__
__main__
>>> type(__builtins__)
<type 'module'>
>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'FloatingPointError', 'IOError', 'ImportError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplementedError', 'OSError', 'OverflowError', 'RuntimeError', 'StandardError', 'SyntaxError', 'SystemError', 'SystemExit', 'TypeError', 'ValueError', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', 'abs', 'apply', 'buffer', 'callable', 'chr', 'cmp', 'coerce', 'compile', 'complex', 'delattr', 'dir', 'divmod', 'eval', 'execfile', 'exit', 'filter', 'float', 'getattr', 'globals', 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'len', 'list', 'locals', 'long', 'map', 'max', 'min', 'oct', 'open', 'ord', 'pow', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'round', 'setattr', 'slice', 'str', 'tuple', 'type', 'vars', 'xrange']
>>> print __builtins__.__doc__
Built-in functions, exceptions, and other objects.
Noteworthy: None is the `nil' object; Ellipsis represents `...' in slices.
>>>
當我們身處新啟動之 Python 交談環境裡,輸入 dir() 可以顯示 local symbol table的名稱串列,共計三個。
進一步提供 __doc__ 給 dir() 做為參數,傳回空串列,表示 __doc__ 物件已無相關屬性 (attributes)。print __doc__ 顯示其為 None 物件。
__name__ 是一個字串物件,表示目前執行「程式」的名稱,其值為 __main__。
__builtins__ 則是一個模組物件,持續以 dir(__builtins__) 觀察,可以顯示模組 __builtins__ 的全部屬性。
顯示 __builtins__ 的 __doc__ 屬性內容。注意到,每個正在執行的主要程式,其程式名稱
(即 __name__ 屬性) 會是 __main__,如果是以模組型態被 import 進來,那麼該模組程式便會以原本檔案名稱為 __name__ 的值。請觀察下列程式範例的說明,兩個極其簡化的「土製模組」。example$ cat other_mod.py
#!/usr/bin/python
print "this is from other_mod."
print __name__
example$ chmod 755 other_mod.py; ./other_mod.py
this is from other_mod.
__main__
example$ cat my_mod.py
#!/usr/bin/python
"""Simple module example for illustrating how __*__ works."""
import other_mod
print "this is from my_mod."
print __name__
example$ chmod 755 my_mod.py; ./my_mod.py
this is from other_mod.
other_mod
this is from my_mod.
__main__
被
import 的模組檔案,其內容會被執行,所以範例 my_mod.py 在執行之後,會先讀進 other_mod.py 的程式片段,接著才是 my_mod.py 的程式片段。請特別留意 __name__ 屬性值的變化,這項控制技巧經常被使用。模組的搜尋路徑
Python 實際搜尋模組路徑的設定值,可以由 sys 模組裡的 path 變數值取得。
>>> import sys
>>> sys.path
['', '/usr/lib/python1.5/', '/usr/lib/python1.5/plat-linux-i386', '/usr/lib/python1.5/lib-tk', '/usr/lib/python1.5/lib-dynload', '/usr/lib/python1.5/site-packages', '/usr/lib/python1.5/site-packages/PIL']
>>> import marr
Traceback (innermost last):
File "<stdin>", line 1, in ?
ImportError: No module named marr
>>>
上述的搜尋路徑是有順序性的,也就是說,
import 所呼叫的模組名稱,依序第一個找到的路徑便會馬上回傳使用。如果找不到 import 的模組名稱,則會傳回 ImportError 的錯誤訊息。以載入 foo 模組為例,其實際尋找模組的過程順序如下:1. 是否存在名為 foo 的目錄,並且裡頭含有該模組的檔案。
2. 是否存在 foo.so、foomodule.so、foomodule.sl 或是 foomodule.dll
3. 是否存在 foo.pyo
4. 是否存在 foo.pyc
5. 是否存在 foo.py
以一個
.py 的 Python 原始碼檔案而言,經過編譯後,會產生一個名為 .pyc 的 bytecode執行檔,當尋找某個模組名稱時,要是 .py 檔案的日期不比 .pyc 檔案來得新,Python 直譯器會直接將編譯好的 .pyc 檔案載入,若是 .py 檔案的日期比 .pyc 檔案來得新,通常就表示 .py 檔案內容已更新,Python 直譯器會重新編譯之,以產生新的 .pyc 檔案,然後才進入載入動作。而 .pyo 檔案只有在直譯器以 -O 選項啟動之後才會產生,這類檔案裡的資訊通常比 .pyc 檔案來得多,包含有原始程式的行號以及除錯資訊,因此 .pyo 檔案的載入速度會較慢,但程式的執行速度會較快。而
.pyc 或是 .pyo 檔案的編譯動作,是在程式裡頭呼叫 import 後才會發生,對 Python語言來說,模組檔案不止是設計概念的切割,它更從強化模組執行效率的角度,鼓勵程式員善用模組檔案功能。如果自製的模組檔案越來越多,其應用自然越顯重要,此時便要認真為自製模組找個適當的存放路徑,比較常見的方式之一,是設定相關的環境變數值,例如變數 PYTHONPATH。●
TABLE_BEGIN●表
: Python 相關環境變數設定
變數名稱 |
說明 |
PYTHONDEBUG |
與 python -d 啟動模式相同。可產生 Python 的語法解析除錯資訊。 |
PYTHONHOME |
與模組搜尋路徑設定相關的變數。 |
PYTHONINSPECT |
與 python -i 啟動模式相同。以交談模式來執行 Python 程式。 |
PYTHONOPTIMIZE |
與 python -O 啟動模式相同。以最佳化模執行 Python 程式。 |
PYTHONPATH |
增加模組搜尋路徑。 |
PYTHONSTARTUP |
交談模式就緒前所執行的程式路徑。 |
PYTHONUNBUFFERED |
與 python -u 啟動模式相同。記錄未做緩衝的二元標準輸出輸入。 |
PYTHONVERBOSE |
與 python -v 啟動模式相同。執行過程詳列相關處理資訊。 |
●
TABLE_END●名稱空間
(namespace) 及其有效領域 (scope)回過頭來,我們再次透過
dir() 來觀察模組檔案載入後的物件變化情況。請將現行目錄設定在 my_mod.py 檔案存放的目錄,然後進入 Python 的交談環境。●圖●
圖
: 名稱空間階層示意●圖●
example$ python
Python 1.5.2 (#1, Sep 30 2000, 18:08:36) [GCC 2.95.3 19991030 (prerelease)] on linux-i386
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> dir()
['__builtins__', '__doc__', '__name__']
>>> import my_mod
this is from other_mod.
other_mod
this is from my_mod file.
my_mod
>>> dir()
['__builtins__', '__doc__', '__name__', 'my_mod']
>>> dir(my_mod)
['__builtins__', '__doc__', '__file__', '__name__']
>>> print my_mod.__doc__
Simple module example for illustrating how __*__ works.
>>> print my_mod.__file__
my_mod.py
>>>
整個運作過程,依序可以分解如下:
1. 每次 Python 啟動時,它會產生一個名為 __main__ 的模組物件,此物件本身當然有其相關資訊,所以我們可以透過 dir() 來取得。
2. 每次呼叫 import 內建函式之後,Python 會載入另一個模組物件,並將其物件相關資訊併進 __main__。
3. 由於兩個模組物件的變數名稱,有若干重複之處,如上述例子裡的 __builtins__、__doc__、__name__,在取用變數資訊時,必須有辦法加以區別才行。
4. Python 區別變數資訊的方法,如上例所示,是類似 __doc__ 與 my_mod.__doc__ 這樣的不同表示法。
5. 每個模組物件,都會伴隨一份物件資訊,以辭典集型別儲存在系統當中,而這樣的物件資訊內容,包括其變數、函式、物件等,都成為辭典集裡所記錄的鍵值或屬性值。
6. 上述的辭典集物件資訊,我們把它簡稱為「名稱空間」(namespace)。
7. 內建函式 dir() 的功能,可以把名稱空間的名稱資訊全部列出。
除了基本的資料型別之外,Python 裡的每一個模組物件都擁有自己的名稱空間。下列是一個範例程式,可以協助列出模組物件的名稱資訊。
example$ cat namespace.py
#!/usr/bin/python
import sys
k = sys.modules.keys()
print "Keys:", k
print "-" * 30
for i in k:
if i == "__main__":
print ">>>", i, "__dict__", sys.modules[i].__dict__
print "-" * 30
print dir()
在實務設計上,
Python 程式員會運用模組的方式,將物件做適當的切割,切割好的程式可以被包在一個 Python 類別庫 (package) 裡,以便進一步有效管理大型的軟體專案。有了函式、模組、類別庫等一系列的架構,我們便可更直覺地管理軟體專案的物件體系。著名的 Zope 系統便是這樣的專案管理實例 [2],其整套系統廣泛應用類別庫的技巧,至今仍在積極發展中。其他有用的函式
為了方便接續的內容,我們先來認識一個實用的模組,稱為
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 之後,利用內建函式 map() 將自製函式 try_ai 與字串串列 piece 連結起來,如此一來,便能如願將 piece 裡的部份字串,轉換成數值型別。顯然 map() 函式在此例中幫上大忙,簡潔地協助我們將自製函式與序列資料做了巧妙結合。接下來,我們就可以進一步稍微改良原本天真的
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 表示式通常還是得花時間熟悉,若是「畫虎不成反類犬」,搞到程式大亂就得不償失了。
相關說明
[1] 有人把缺少 return 敘述的「函式」稱為 procedure。本質上,由於函式不明確描述 return 的話,會自動以 None 值 (空型別) 傳回,所以視之為函式並無不妥。
[2] Zope 是一套網站 Application Server 系統,完全由 Python 語言所開發出來,程式本身就是一個龐大的物件系統,有興趣的朋友,可到網站 http://www.zope.org/ 觀摩。
[3] 來點不一樣的吧,歡迎訪閱 Instant Python 網頁,網址 http://www.hetland.org/python/instant-python.php
[4] 想讓 Python 執行更有效率嗎? 試試Python Performance Tips 網頁,網址http://musi-cal.mojam.com/~skip/python/fastpython.html
[5] Python for Lisp Programmers 網頁,網址 http://www.norvig.com/python-lisp.html
[6] A Comparison of Python and LISP 網頁,網址http://www.strout.net/python/pythonvslisp.html
[7] A Python/Perl phrasebook 網頁,網址http://starship.python.net/~da/jak/cookbook.html
[8] Major Python changes 網頁,提供Python 改版增修重點說明,網址http://shell.rmi.net/~lutz/errata-python-changes.html
[9] Python Language Mapping 網頁,網址http://www.informatik.hu-berlin.de/~loewis/python/pymap.htm