Python
程式語言教學目標
: 認識 Python 語言特性,熟悉基本類別使用技巧,了解類別庫等物件模組運作及應用方式。教學內容
: Introduction: 特色與應用場合。 Basic Types: 數值、字串、串列、辭典集 。 Structure: 流程控制、函式、模組。 Useful Modules: 系統常用之模組工具。 Object: 物件結構及類別庫。 Demo: Python程式範例。
A language that makes it hard to write elegant code makes it hard to write good code.
-- Eric S. Raymond
特色
延革
可參考物件導向式語言之簡介,網址
http://catalog.com/softinfo/objects.html。
Zope程式人員開發手冊
http://www.zope.org/Documentation/ZDG 。
通信論壇管理程式
mailman 網站 http://www.list.org/http://www.tuxedo.org/~esr/cml2/index.html
學習
Python 之相關先備知識物件觀念,
shell 環境的操作,以及檔案系統結構,都是重要的先備知識,另外,C 語言的基本觀念與技巧,也會有助於 Python 的學習,比如說,事先看過 A Book on C (作者是 Al Kelley 與 Ira Pohl) 的內容。這些簡單的先備知識,可以成為多種程式語言學習背景支撐,建議不妨抽空予以納入。線上資源
程式語言的學習方法,其原則與日常語言極其相似。
「應用於日常生活」及「依樣畫葫蘆」,應是兩條學習捷徑。
在生活裡尋找程式語言的應用機會,能讓語言學習增添動機與樂趣,模仿前人的程式範例,可省茫然摸索的時間浪費,進而積累舉一反三之效。
星艦
(Starship Python) 網站裡的艦員名單裡,高手如雲,通常他們也樂於分享自己的學習經驗,不妨抽空前往取經。http://starship.python.net/crew.html安裝
Python下載網址
http://downloads.activestate.com/ActivePython/windows/2.1/ActivePython-2.1.1.msi
進入交談式環境
Linux: $ python
Windows: python (command line)
ActivePython: Python Win Environment
離開交談式環境
送出
EOF 字元Linux: Ctrl + D
Windows: Ctrl + Z
ActivePython: File - Exit
計算機功能
特別注意到上述的
_ (底線) 符號,它代表的是前一個計算值的結果。還可以呼叫一些內建函式或載入函式模組。
>>> round(3.14159, 4)
3.1416
>>> abs(-3.000000001)
3.000000001
>>> import math
>>> math.sqrt(2)
1.41421356237
基本型別
字串
(string)、串列 (list)、值組 (tuple) 屬於 Python 的序數型別 (sequence)。辭典集 (dictionary) 屬於 Python 的映射物件 (mapping object)。
example$ python
Python 2.2.1 (#1, Aug 30 2001, 17:36:05) [GCC 2.96 20000731 (Mandrake Linux 8.1 2.96-0.61mdk)] on linux-i386
Type "copyright", "credits" or "license" for more information.
>>> x = 5 + 14 / 7 - 3 * 2
>>> x
1
>>> 5 / 2 u
2
>>> 5.0 / 2
2.5
>>> 2 ** 8 v
256
>>> 10 ** 9
1000000000
>>> 10 ** 10 u
Traceback (innermost last):
File "<stdin>", line 1, in ?
OverflowError: integer pow()
>>> 10L ** 10 v
10000000000L
>>> x = 2147483647 w
>>> x
2147483647
>>> x + 1 x
Traceback (innermost last):
File "<stdin>", line 1, in ?
OverflowError: integer addition
>>> 7.3 ** 2.4
118.025232408
>>> 3.5e8 * 2.0e7 u
7e+15
>>> x = 1.00000000001
>>> x
1.00000000001
>>> x = 1.000000000001 v
>>> x
1.0
>>> x = (3 + 2j) * (4 + 9j) u
>>> x
(-6+35j)
>>> x.real v
-6.0
>>> x.imag v
35.0
流程控制
example$ cat print_pr.py
principal = 1000
rate = 0.05
num_years = 5
year = 1
while year <= num_years : u
principal = principal * (1 + rate)
print year, principal v
year = year + 1
v 如果有人對於輸出格式不夠滿意的話,可以採用 print "%3d %0.4f" % (year, principal) 敘述式,則可以得到新的輸出結果:
1 1050.0000
2 1102.5000
3 1157.6250
4 1215.5063
5 1276.2816
example$ cat high_low.py
number = 78
guess = 0
while guess != number :
guess = input(
“Guess a number: “)if guess > number :
elif guess < number :
example$ cat ascii.py
#!/usr/bin/python
i = 0
while i < 128 :
print chr(i), u
if i != 0 and i % 16 == 0 : v
i = i + 1
物件型別比較
字串
(string)、串列 (list)、值組 (tuple) 屬於 Python 的序數型別 (sequence)。辭典集 (dictionary) 屬於 Python 的映射物件 (mapping object)。
在 Python 當中,只要將幾個文字包含在單引號、雙引號、三引號裡,就可以建立一個字串:
>>> a = 'hello world'
>>> b = "It's nice to learn Python"
>>> c = """
... this is a triple quote demo.
... see? we can go even cross lines :)
... remember to end it with another triple quote.
... """
>>> a[0]
'h'
>>> b[5:18]
'nice to learn'
>>> c[-22:]
'another triple quote.\012'
串列 (list) 是以中括符號為設定格式,下列是其操作特性:
>>> x = [1, 2.0, 7, "eleven"]
>>> x[1]
2.0
>>> x[-2]
7
我們可以使用分割運算 (slice),來截取串列項目裡的某段子集,由於這項操作技巧太實用了,建議不但要搞懂,最好還能熟練。
>>> y = ["first", "second", "third", "fourth"]
>>> y[1:-1] u
['second', 'third']
>>> y[:3] v
['first', 'second', 'third']
>>> y[-2:] w
['third', 'fourth']
>>> y[-2:-3] x
[]
以串列 y 為例,其完整的索引 (index) 設定狀況分析如下:
y = |
[ |
"first" |
, |
"second" |
, |
"third" |
, |
"fourth" |
] |
||
↑ |
↑ |
↑ |
↑ |
↑ |
|||||||
正向索引 |
0 |
1 |
2 |
3 |
4 |
||||||
負向索引 |
-4 |
-3 |
-2 |
-1 |
u 原則上,分割運算是採用 [m:n] 格式,m 為起點,n 為終點,不管是利用正向索引或是負向索引皆可。搭配圖示對照後,可以很輕易地看出 y[1:-1] 指的是
“second” 與 “third” 所組成的子串列,也就稱為串列 y 的一個「切片」(slice)。v 分割運算若省略「指明的」起點,則代表從最前面的元素取起。
w 分割運算若省略「指明的」終點,則代表取至最後面的元素為止。
x 當起點落在終點之後時,分割運算的結果會是空串列。
接著,我們介紹的是值組 (tuple) 型別,乍看之下,它與串列的運作方式類似,不同處在於值組是以小括號符號為設定格式,串列是以中括號為設定格式,而且值組是不可變的物件,串列是可變的物件。
>>> t = ("this", "is", "a", 6, "element", "tuple") u
>>> len(t) v
6
>>> max(t) w
'tuple'
>>> min(t) x
6
另外,序數型別既然包括字串、串列、值組三種,在操作應用時,三者是具有共通之運算元的,比如之前已介紹過的索引運算、分割運算,以及 len()、min()、max() 等。
物件的可變與不可變
什麼是「可變的」物件呢? 允許使用者對物件進行新增、修改、刪除等動作,該物件則稱為「可變的」物件。事實上,每一種儲存在 Python 程式裡的資料,都是以物件的方式存在,而每一個物件的組成,則包括有識別字、資料型別,以及物件的值。當一個物件建立完畢之後,它的識別字和型別都不可以再更改,但它裡面的值卻有可能被修改,而可以被修改者,就是可變物件 (mutable),否則稱為不可變物件 (immutable)。
>>> a = 256
>>> b = 'Python'
>>> print id(a), id(b) u
134957540 134969832
>>> a is b v
0
>>> print type(a), type(b) w
<type 'int'> <type 'string'>
u 使用內建函式 id() 可以取得物件的識別字資訊,不過,大致只能得到一長串不甚具備意義的數字,藉以了解 a 與 b 「確實」是兩個不同的物件。
v 使用 is 運算元可以直接比較兩個物件的識別字是否相同,由於我們知道 a 與 b 的識別字確實不同,因此運算結果是 0。
w 使用內建函式 type() 可以取得物件的型別資訊。
>>> y = [
“first”, “second”, “third”, “fourth”]>>> id(y)
135309760
>>> y[2] = '3rd' u
>>> y.append('5th') v
>>> y
['first', 'second', '3rd', 'fourth', '5th']
>>> id(y) x
135309760
>>> y.sort() w
>>> y
['3rd', '5th', 'first', 'fourth', 'second']
>>> id(y) x
135309760
u 以索引運算來指定想要變更的資料值。
v 以物件方法 append() 來增加 '5th' 字串到串列 y 的最後面。
w 以物件方法 sort() 來將串列元素排序。
x 特別注意到,經過上述的操作及修改過程,串列 y 的識別字仍維持相同,表示串列 y 是可變物件。
連續數值的指定
: 內建函式 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]
下列的簡單範例,可以將一組資料進行編號。
>>> 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(128) :
print chr(i),
if i != 0 and i % 16 == 0 :
相較之下,程式碼本身顯得簡化了,但語法型態與
while 不同,讀者應用時大抵挑自己習慣者即可。
語法結構
Python 識別字指定規則:
http://www.python.org/doc/2.1/ref/identifiers.html
Python 所有保留字資訊:
http://www.python.org/doc/2.1/ref/keywords.html
函式指定方式
以保留字
def 為首,並依照縮排規則撰寫內容。慣例上,函式的第二行會是一段「三引號註釋字串」。
文件字串
(documentation string)""" 所括夾的文字,稱為「文件字串」。
可以透過如 fact.__doc___ 物件方法,來顯示文件字串內容。
>>> 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
>>>
fact() 是一個階乘函式範例,請注意到最後一行的保留字 return,如果少了 return 敘述,則預設會以 None 值傳回。以範例函式 fact() 來看,n 是函式的參數 (argument),其傳回值 r 是 n 的階乘結果。
>>> 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)。下列的例子,將參數設定的可能狀況大抵做了整合介紹,我們可以一窺函式參數派定的相關細節,值得讀者反覆測試觀察。
>>> 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),也就是由一個物件集合來作為另一個物件集合的鍵值索引。映射物件和之前談過的序數資料相較,在概念上有擴充、補強的涵意,善用映射物件的特性,可以協助我們將看似複雜的問題,以相當直覺的方式解決。兩者具有若干不同之處,例如序數以索引運算作為其取值方式,映射物件的元素成份來源很有彈性,映射物件是不做排序的,而且映射物件是可變物件。在功能上,辭典集的實作相當於資料結構裡的雜湊表
(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')]
>>>
注意到辭典集的儲存,並沒有特定的順序方式,如果想要依特定的排序方法處理資料,可以另尋變通方法。>>> 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'
>>>
透過 update() 物件方法,我們更新了 Beatles 鼓手的設定資料。
get() 物件方法是詢問 Beatles 裡是否有 leader 此一鍵值,若存在則傳回其對應之元素值,否則會傳回後頭的字串資料。表
: 辭典集的方法和操作
項目 |
說明 |
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。 |
模組的使用
以
Linux Mandrake 8.1 為例,其安裝目錄為 /usr/lib/python2.1。目錄裡包含類似 string.py、os.py、find.py 的檔案,我們可以透過 import string, os, find 之類的程式語法呼叫這些模組內容。也可以直接閱讀這些 .py 檔案的程式碼,相信部份檔案的內容,對你而言已不再全是天書。實際動手撰寫自己的模組之前,我們得先認識內建函式
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
>>>
以載入
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。表
: Python 相關環境變數設定
變數名稱 |
說明 |
PYTHONDEBUG |
與 python -d 啟動模式相同。可產生 Python 的語法解析除錯資訊。 |
PYTHONHOME |
與模組搜尋路徑設定相關的變數。 |
PYTHONINSPECT |
與 python -i 啟動模式相同。以交談模式來執行 Python 程式。 |
PYTHONOPTIMIZE |
與 python -O 啟動模式相同。以最佳化模執行 Python 程式。 |
PYTHONPATH |
增加模組搜尋路徑。 |
PYTHONSTARTUP |
交談模式就緒前所執行的程式路徑。 |
PYTHONUNBUFFERED |
與 python -u 啟動模式相同。記錄未做緩衝的二元標準輸出輸入。 |
PYTHONVERBOSE |
與 python -v 啟動模式相同。執行過程詳列相關處理資訊。 |
請將現行目錄設定在
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
>>>
除了基本的資料型別之外,
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) 裡,以便進一步有效管理大型的軟體專案。有了函式、模組、類別庫等一系列的架構,我們便可更直覺地管理軟體專案的物件體系。圖
: 名稱空間階層示意其他有用的函式
為了方便接續的內容,我們先來認識一個實用的模組,稱為
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]
>>>
>>> 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 表示式通常還是得花時間熟悉,若是「畫虎不成反類犬」,搞到程式大亂就得不償失了。
存取檔案系統資訊
>>> 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.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") |
物件的使用
class Circle:
pass
myCircle = Circle()
myCircle.radius = 5
print 2 * 3.14 * myCircle.radius
定義一個 Circle 類別,其內容「空空如也」。
建立一個新個體,名為 myCircle。
設定 myCircle 的 radius 值為 5。其設定方式,是在個體物件名稱後加上 . 符號,成為 myCircle.radius 這樣的描述方式。個體物件的設定值,可由
__init__ 這個起始函式自動完成,通常 __init__ 又被稱為類別的「建構子」(constructor)。每次有新的個體物件被建立,都會找尋 __init__ 的設定內容,自動完成新個體物件的起始設定值。
class Circle:
def __init__(self):
self.radius = 1
def area(self):
return self.radius * self.radius * 3.14159
myCircle = Circle()
print 2 * 3.14 * myCircle.radius
myCircle.radius = 5
print 2 * 3.14 * myCircle.radius
print myCircle.area()
類別 Circle 裡有個 __init__() 函式,設定其 radius 值為 1。注意到 self 這個參數,慣例上它就是 __init__() 函式的第一個參數,當 __init__ 起始設定時,self 就會被設定為新建立的個體上。
新個體物件建立後,有自己的個體變數 (instance variables),如 radius 即為一例。
個體變數的值可以重新設定。
可以為個體物件設定物件方法 (method),其方式就像定義函式一般。class Circle:
def __init__(self, r=1):
self.radius = r
def area(self):
return self.radius * self.radius * 3.14159
c = Circle(5)
print c.area()
上述則是另一個改進版本的
Circle 類別內容設定,增加預設變數 r,並指定其預設值為 1。如此一來,我們便可透過 Circle(5) 這樣的方式來指定新個體的建立。
物件繼承
(inheritance) 範例類別庫
一旦模組
(module) 的運作模式熟悉了,則類別庫 (package) 便不難理解,因為類別庫只是在一個目錄之下,將一堆功能相近而能相互引用的程式碼檔案收集在一起。而呼叫類別庫時,則是以其上層目錄名稱來命名。example$ cat mathproj/__init__.py
print "Hello from mathproj init"
__all__ = ['comp']
version = 1.03
example$ cat mathproj/comp/__init__.py
__all__ = ['c1']
print "Hello from mathproj.comp init"
example$ cat mathproj/comp/c1.py
x = 1.00
example$ cat mathproj/comp/numeric/__init__.py
print "Hello from nemeric init"
example$ cat mathproj/comp/numeric/n1.py
from mathproj import version
from mathproj.comp import c1
from n2 import h
def g():
print "version is", version
print h()
example$ cat mathproj/comp/numeric/n2.py
def h():
print "Called function h in module n2"
執行來測試上述的類別庫程式內容時,必須確認
mathproj 目錄位於 Python 的搜尋路徑中,最簡單的方式,便是使用者的現行目錄設定在 mathproj 目錄之上,然後再開始一個 Python 的對話環境。>>> import mathproj
Hello from mathproj init
>>> mathproj.version
1.03
>>> mathproj.comp.numeric.n1
Traceback (innermost last):
File "<stdin>", line 1, in ?
AttributeError: comp
>>> import mathproj.comp.numeric.n1
Hello from mathproj.comp init
Hello from nemeric init
>>> mathproj.comp.numeric.n1.g()
version is 1.03
Called function h in module n2
None
產生密碼檔內容
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/59873
如果你需要隨機產生密碼檔的輸入值,本程式可以依照「密碼長度」、「是否含有數字」兩項條件來輸出結果。
計算目錄容量大小
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/86554
類似
Unix 之 du 工具程式,可用來計算 Windows 系統裡目錄所佔的空間大小,指令選項包括 k、m。動態更改
Python 系統路徑http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52662
傳回值為三類:
-1 新增失敗,目錄不存在
1 新增成功
0 新增失敗,路徑已存在