Python 語法隨手記錄 – 1

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

Python 教學文件

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


///// 跳出程式執行

import sys; sys.exit()

///// 命令列參數的取得

sys.argv[0]

///// 互動式啟動檔(startup file)

import os
filename = os.environ.get(‘PYTHONSTARTUP’)
if filename and os.path.isfile(filename):
execfile(filename)

///// comment 的寫法

# this is the first comment
SPAM = 1 # and this is the second comment
# … and now a third!
STRING = “# This is not a comment.”

///// 特殊變數 “_”

在互動模式之下,最後一個印出來的 expression 的值會儲存在一個特殊變數 “_” 之中。這表示,當你用Python的直譯器來當作計算機用的時候,想要連續做運算其實是方便許多的。如下例:
>>> tax = 17.5 / 100
>>> price = 3.50
>>> price * tax
0.61249999999999993
>>> price + _
4.1124999999999998
>>> round(_, 2)
4.1100000000000003

對於使用者來說, “_” 這個變數是一個唯讀的變數。你沒有辦法設定一個值給它,當你這樣做的時候,事實上你是重新創造一個同名的變數,但是跟之前系統內建的 “_” 這個變數是一點關係也沒有的了。

///// 字串

除了數字之外,Python 也有能力處理字串(string)。字串在 Python 中有很多種表達方式,它可以放在雙括號 ”” 之中,也可以放在單括號 ’’ 裡面:

>>> ‘spam eggs’
‘spam eggs’
>>> ‘doesn\’t’
“doesn’t”
>>> “doesn’t”
“doesn’t”
>>> ‘”Yes,” he said.’
‘”Yes,” he said.’
>>> “\”Yes,\” he said.”
‘”Yes,” he said.’
>>> ‘”Isn\’t,” she said.’
‘”Isn\’t,” she said.’

字串常數(string literals)是可以跨越多行的,其表示方法有很多。如果要換行的話可以用”\”符號來表示之。如下例:

hello = “This is a rather long string containing\n\
several lines of text just as you would do in C.\n\
Note that whitespace at the beginning of the line is\
significant.\n”
print hello

這個例子會印出以下的結果:

This is a rather long string containing
several lines of text just as you would do in C.
Note that whitespace at the beginning of the line is significant.

你也可以用成對的三個單引號( “”” ) 或雙引號 ( ”’ ) 來表示字串。在此情況下你所打入的 ENTER 就會直接被解讀為換行符號而不需要再用 \n 了。

print “””
Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to
“””

這個例子會印出以下的結果:

Usage: thingy [OPTIONS]
-h Display this usage message
-H hostname Hostname to connect to

字串可以用 + 這個運算元來相加 (連接起來),或是用 * 這個運算元來重複之。請看例子:

>>> word = ‘Help’ + ‘A’
>>> word
‘HelpA’
>>> ‘<‘ + word*5 + ‘>’
‘<HelpAHelpAHelpAHelpAHelpA>’

如果你把兩個字串常數放在一起,它們自動就會相加起來。所以,上面的例子的第一行也可以寫作 “word = ‘Help’ ‘A'” 。不過這個方法只適用於兩個字串常數的相加,其他情況就不適合了。請看例子:

>>> import string
>>> ‘str’ ‘ing’ # <- This is ok
‘string’
>>> string.strip(‘str’) + ‘ing’ # <- This is ok
‘string’
>>> string.strip(‘str’) ‘ing’ # <- This is invalid
File “<stdin>”, line 1
string.strip(‘str’) ‘ing’
^
SyntaxError: invalid syntax

如同在 C 語言一樣,字串是有標記(subscript(index))的,第一個字元的標記(subscript(index))就是 0。在 Python 中沒有另外一個字元 character 資料型態,一個字元就是一個長度為 1的字串。就像是在 Icon 語言一樣,字串是可以用其 subscript(index) 來切出( slice notation )其中的一部份的,其語法為 “”。

>>> word[4]
‘A’
>>> word[0:2]
‘He’
>>> word[2:4]
‘lp’

與C不同的是,Python 的字串是不可改變的(immutable),如果你想要改變其中的一個字元或是一個部份(slice),你會得到一個錯誤的信息:

>>> word[0] = ‘x’
Traceback (innermost last):
File “<stdin>”, line 1, in ?
TypeError: object doesn’t support item assignment
>>> word[:-1] = ‘Splat’
Traceback (innermost last):
File “<stdin>”, line 1, in ?
TypeError: object doesn’t support slice assignment

但是你可以任意使用一個字串的一個字元或是一個部份(slice)來創造出另一個字串,這是完全可行的:

>>> ‘x’ + word[1:]
‘xelpA’
>>> ‘Splat’ + word[-1:]
‘SplatA’

當你用字串切片(string slice)的語法時,可以使用其預定(default)的 subscript(index) 值,這是很方便的。第一個 subscript(index) 的預設值是 0,第二個 subscript(index) 的預設值則是這個字串的整體長度。

>>> word[:2] # The first two characters
‘He’
>>> word[2:] # All but the first two characters
‘lpA’

所以, s[:i] + s[i:] 會恰好等於 s 。你可以想一想為什麼:

>>> word[:2] + word[2:]
‘HelpA’
>>> word[:3] + word[3:]
‘HelpA’

如果你用一些奇怪的 index 來切割字串,Python 直譯器也都處理的很好:如果第二個 index 太大的話就自動代換為字串的長度,如果第二個 index 比第一個 index 還要小的話就自動傳回一個空字串。

>>> word[1:100]
‘elpA’
>>> word[10:]

>>> word[2:1]

字串的 index 甚至可以是負數,若是負數的話,就必須從字串的尾巴開始算起。如下例:

>>> word[-1] # The last character
‘A’
>>> word[-2] # The last-but-one character
‘p’
>>> word[-2:] # The last two characters
‘pA’
>>> word[:-2] # All but the last two characters
‘Hel’

但是 -0 事實上是等於 0 ,所以不會從尾巴開始算起。

>>> word[-0] # (since -0 equals 0)
‘H’

如果負數 index 超過字串的範圍的話,就自動只會到最大可能的範圍,但是如果不是切割一部份的話就會造成錯誤的情形:

>>> word[-100:]
‘HelpA’
>>> word[-10] # error
Traceback (innermost last):
File “<stdin>”, line 1
IndexError: string index out of range

最好避免錯誤的方法是把 index 看成是指向字元及字元間位置的指標,字串的最開頭是 0,字串的結尾處就是字串的長度。如下圖所示:

+—+—+—+—+—+
| H | e | l | p | A |
+—+—+—+—+—+
0 1 2 3 4 5
-5 -4 -3 -2 -1

上圖的數字部分第一行代表的是正數的 index,由 0 到字串的長度,第二行代表的是負數的 index。字串的切片(slice)很容易就可以看出來,就是兩個 index 之間的所有字元組合成的字串囉。

對於正數的 index 來說,如果兩個 index 都在範圍之內,字串的切片(slice)的長度就正好是其兩個 index 相減的結果。舉例來說 word[1:3] 的長度就正好是 2。

Python內建的 len() 函式可以幫助我們得到字串的長度值。

>>> s = ‘supercalifragilisticexpialidocious’
>>> len(s)
34

///// Unicode 字串, 從 Python 2.0 開始 Python 支援

在 Python 中要創造一個 Unicode 字串就跟創造一個普通字串一樣容易:

>>> u’Hello World !’
u’Hello World !’

在引號之前小寫的 “u” 代表這個字串是一個 Unicode 字串。如果你要用到特殊字元,你可能要使用 Python 的 Unicode 特殊字元編碼( Unicode-Escape encoding)。底下的範例示範如何使用之:

>>> u’Hello\\u0020World !’
u’Hello World !’

上面的 u0020 表示在這個位置要插入一個由十六位元 0x0020 所代表的 Unicode 字元 (就是空白字元啦)。
其他的字元也是一樣的會被解讀為其對應的 Unicode 字元。由於 Unicode 對應中的前 256 個 Unicode 字元正好就是大部分歐美國家使用的 Latin-1 編碼字元,所以其轉換是更加的容易。

對於專家們來說,有一個字串的原始模式(raw mode)可以使用。你必須再加上一個小寫 ‘r’ 來使 Python 使用這一個原始的 Unicode 特殊字元編碼( Raw-Unicode-Escape encoding)。只有當
uXXXX 之中的小寫 ‘r’ 有奇數的’\’時才會用到這一個編碼的。

>>> ur’Hello\u0020World !’
u’Hello World !’
>>> ur’Hello\\u0020World !’
u’Hello\\\\u0020World !’

這個原始模式(raw mode)通常用在當你的字串裡面有一大堆的反斜線 ‘\’ 時 ,例如 regular expressions(正規表示)時就常用到。

除了這些標準的編碼之外, Python 還提供了一整套的方法讓你可以從以知的編碼中創造出 Unicode 字串來。

Python內建的 unicode() p() 函式可以讓你使用所有的已註冊的 Unicode 解碼/編碼系統(codecs (COders and DECoders))。 這個 codes 可以與大部分的系統互相轉換,包括 Latin-1, ASCII , UTF-8 以及 UTF-16 等等。上面所提到的最後兩種系統是可變長度的編碼系統,可以來儲存8位元及16位元的 Unicode 字元。Python 預設使用 UTF-8 為預設編碼系統。當你印出 Unicode 或是將 Unicode 寫入檔案時都會使用到。

>>> u”a”o”u””
u’\344\366\374′
>>> str(u”a”o”u””)
‘\303\244\303\266\303\274’

如果你要使用一個特別的編碼系統,但是要印出對應的 Unicode 碼時,你可以使用 unicode() 函式,加上這個編碼系統的名稱當作第二個參數。

>>> unicode(‘\303\244\303\266\303\274′,’UTF-8′)
u’\344\366\374’

如果要把 Unicode 字串轉換為一般的字串編碼時,可以使用 Unicode 物件的 encode() 方法(method)。

>>> u”a”o”u””.encode(‘UTF-8’)
‘\303\244\303\266\303\274’

///// 列(List)

Python 能夠了解一些較為 複雜 的資料型態,這些資料型態大多是用來處理一群的其他資料值。最方便使用的要算是 list 了,一個list可以寫成一串由逗號分開的值(東西),然後用角括號括起來便成。放在 list 裡的東西不需要是同一個資料型態

>>> a = [‘spam’, ‘eggs’, 100, 1234]
>>> a
[‘spam’, ‘eggs’, 100, 1234]

跟字串的 index 用法相同,list 的 index 也由 0 開始,同樣你可以用 index 來切割 lists、組合兩個 list 等等:

>>> a[0]
‘spam’
>>> a[3]
1234
>>> a[-2]
100
>>> a[1:-1]
[‘eggs’, 100]
>>> a[:2] + [‘bacon’, 2*2]
[‘spam’, ‘eggs’, ‘bacon’, 4]
>>> 3*a[:3] + [‘Boe!’]
[‘spam’, ‘eggs’, 100, ‘spam’, ‘eggs’, 100, ‘spam’, ‘eggs’, 100, ‘Boe!’]

與字串不相同的是,字串的個別字元是不可變動的( immutable ),但是 list 的個別成員是可以自由改變的。

>>> a
[‘spam’, ‘eggs’, 100, 1234]
>>> a[2] = a[2] + 23
>>> a
[‘spam’, ‘eggs’, 123, 1234]

你也可以設定一個值或是一個 list 給一個 list 的切割部分(slice),但是這樣的結果會改變整個 list 的長度:

>>> # Replace some items:
… a[0:2] = [1, 12]
>>> a
[1, 12, 123, 1234]
>>> # Remove some:
… a[0:2] = []
>>> a
[123, 1234]
>>> # Insert some:
… a[1:1] = [‘bletch’, ‘xyzzy’]
>>> a
[123, ‘bletch’, ‘xyzzy’, 1234]
>>> a[:0] = a # Insert (a copy of) itself at the beginning
>>> a
[123, ‘bletch’, ‘xyzzy’, 1234, 123, ‘bletch’, ‘xyzzy’, 1234]

內建的 len() 函式仍然可用在 list 上面:

>>> len(a)
8

一個 list 也可以是另一個 list 的成員(這叫作巢狀 list, nested list),參考下例:

>>> q = [2, 3]
>>> p = [1, q, 4]
>>> len(p)
3
>>> p[1]
[2, 3]
>>> p[1][0]
2
>>> p[1].append(‘xtra’) # See section 5.1
>>> p
[1, [2, 3, ‘xtra’], 4]
>>> q
[2, 3, ‘xtra’]

注意前一個例子, p[1] 以及 q 事實上指得是同一個物件。我們在之後還會再討論物件的語法( object semantics )。

當然 Python 能做比二加二更有用更複雜的事,例如說,我們可以寫一個程式來印出費氏數列( the Fibonacci series )來:

>>> # Fibonacci series:
… # the sum of two elements defines the next
… a, b = 0, 1
>>> while b < 10:
… print b
… a, b = b, a+b

1
1
2
3
5
8

這個範例告訴了我們很多新的事情:

程式的第一行是一個多重設定( multiple assignment ):兩個變數 a 以及 b 同時都設定了新的值 0 與 1。 程式的最後一行再次使用這個技巧,這次在設定符號(等號)的右邊我們使用了 expression,所有在右邊的 expression 會先求得其值(evaluate)然後才進行設定(assign)的動作。對於在右邊的 expression 來說,其 evaluate 的次序則是由左至右的。

在 while 迴圈中,只要條件符合(在這裡是 b < 10 ), 這個 while 迴圈就會一直執行。與 C 相同的是,對 Python 而言只要是非零的整數都代表在決定 true/false 的情況下都代表 true,0 則代表 false。我們也可以在迴圈的條件的地方放入字串或是一個 list,只要這個字串或 list 的長度不是零就代表 true,若是空字串或空的 list 就代表 false。在這個例子裡,我們比較兩個值的大小。比較的運算元與C是完全相同的: < (小於), > (大於), == (等於), <= (小於或等於), >= (大於或等於) 以及 != (不等於)。

在迴圈中的執行部分是 縮排 的:縮排在 Python 中是表示一群敘述的方法(way of grouping statements)。Python 沒有(還沒有)提供夠聰明的行排版機制,所以每個要縮排的行你都得打入空白鍵或是tab鍵來縮排。實際的工作環境中,你也許會有自己的文字編輯器,大部分的編輯器會自動幫你做縮排的工作。當在互動模式下輸入一個複合的 statement 時(一個由許多 statements 組合成的 statement),最後你需要再打入一個空白行(譯:按 ENTER 鍵)來告訴直譯器這個 statement 已經完成了(直譯器沒辦法猜你什麼時候完成這個 statement)。值得注意的是,如果你的 statement 是屬於同一群(block)的話,你縮排的距離就要是一樣的。

print 這個敘述會印出一個 expression 的結果值,這點與我們之前所做的僅僅打入 expression 是不同的。不同之處在於對字串及多個的 expression 來說,用 print 不會印出字串的引號,也會在多個 expression 之間印出空白來,這樣會讓結果好看一點。如下所示:

>>> i = 256*256
>>> print ‘The value of i is’, i
The value of i is 65536

如果不想每次的輸出都換行的話可以在 print 敘述之後加上逗號,如下所示:

>>> a, b = 0, 1
>>> while b < 1000:
… print b,
… a, b = b, a+b

1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

值得注意的是如果最後一行還沒有完成的話,直譯器會在印出 prompt 之前印出新的一行。

///// if 敘述

大概最為人所知的 statement 就是 if 敘述了,舉例如下:

>>> x = int(raw_input(“Please enter a number: “))
>>> if x < 0:
… x = 0
… print ‘Negative changed to zero’
… elif x == 0:
… print ‘Zero’
… elif x == 1:
… print ‘Single’
… else:
… print ‘More’

elif 的部份可以沒有也可以有很多個, else 部分可以有一個也可以沒有。 `elif’ 這個關鍵字是 `else if’ 的簡化,而且有減少過分縮排的效果。 用 if … elif … elif … 這樣的寫法可以來取代在其他一些程式語言中常見的 switch 或是 case 的寫法。

///// for 敘述

在 Python 裡的 for 敘述的用法與在 C 或是 Pascal 裡的用法有所不同。不像是在 Pascal 中一定要執行某個數目的迴圈,也不像是在 C 中讓使用者決定執行的進度(step)及結束執行的條件,Python 的 for 敘述會將一個系列(sequence,像是 list 或是 string)裡所有的成員走遍一次,執行的順序是依照成員在 squence 裡的順序。以下是一個例子:

>>> # Measure some strings:
… a = [‘cat’, ‘window’, ‘defenestrate’]
>>> for x in a:
… print x, len(x)

cat 3
window 6
defenestrate 12

在迴圈的執行之中改變 sequence 的內容是危險的一件事(當然,只有可變的 sequence 像 list 才能作更動),如果你真的需要在迴圈的執行中改變 list 的成員值,最好先複製一份這個 list 的拷貝,然後針對這個拷貝來做迴圈。list 的切割(slice)提供了一個簡便的製作拷貝的方法:

>>> for x in a[:]: # make a slice copy of the entire list
… if len(x) > 6: a.insert(0, x)

>>> a
[‘defenestrate’, ‘cat’, ‘window’, ‘defenestrate’]

///// range() 函式

如果你真的需要一個迴圈執行一定數目的次數的話,你可以使用內建的 range() 函式。這個函式會產生一個含有逐步增加數字的 list。如下:

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

在這個函式中的所傳入的參數是代表端點,而且這個端點不在產生的 list 之中。 range(10) 正好產生10個數值,正好是這個 list 的 index 是由 0 到 10。我們也可以讓這個產生的 list 從某個數值開始,或者規定其每次增加的數值為多少 (增加值也可以是負數,這個增加值也叫做 `step’)。

>>> range(5, 10)
[5, 6, 7, 8, 9]
>>> range(0, 10, 3)
[0, 3, 6, 9]
>>> range(-10, -100, -30)
[-10, -40, -70]

所以如果我們要循環一次一個 sequence 的 index 的話,我們可以用 range() 配合上 len() 一起使用:

>>> a = [‘Mary’, ‘had’, ‘a’, ‘little’, ‘lamb’]
>>> for i in range(len(a)):
… print i, a[i]

0 Mary
1 had
2 a
3 little
4 lamb

///// break 及 continue 敘述,以及在迴圈中的 else 子句

如同在 C 語言裡一樣, break 敘述中斷最靠近的一個 for 或 while 迴圈。

同樣的,從 C 語言借過來的 continue 敘述會中斷目前執行的迴圈,並且執行下一個循環。

特別的是,Python 的迴圈有一個 else 子句,這個子句之後的程式碼會在整個迴圈正常結束的時候執行,(對 for) 迴圈而言指的是 list 已經到底,對 while 迴圈而言指的是條件式變成 false)。但是,若是在非正常結束(因為 break 敘述)的情況下 else 子句的程式碼就不會執行。底下的例子是一個迴圈,用來找出所有的質數:

>>> for n in range(2, 10):
… for x in range(2, n):
… if n % x == 0:
… print n, ‘equals’, x, ‘*’, n/x
… break
… else:
… print n, ‘is a prime number’

2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

///// pass 敘述

pass 敘述什麼也不做,通常是用在當你的程式的語法上需要有一個敘述,但是卻不需要做任何事的時候。例子如下:

>>> while 1:
… pass # Busy-wait for keyboard interrupt

///// 定義函式

我們可以定義一個函式,在底下這個函式定義的例子,當我們給定想要印出的範圍,這個函式會印出一個費氏數列來:

>>> def fib(n): # write Fibonacci series up to n
… “Print a Fibonacci series up to n”
… a, b = 0, 1
… while b < n:
… print b,
… a, b = b, a+b

>>> # Now call the function we just defined:
… fib(2000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

在上例中這個 def 關鍵字代表了一個函式的定義(function definition),在 def 之後必須接著函式的名稱以及一個用括號括起來的一連串的參數。接下來一行之後的程式碼就是函式的主體部分,而且必須是縮排的。函式的程式碼部分的第一個 statement 可以是一個字串常數(string literal),這個字串常數會被當作是函式的註解部分而叫做註解字串(documentation string或是 docstring )。

有工具可以使用這個註解字串來自動的製作出線上的或是印出來的文件,或者是讓使用者可以互動式的瀏覽程式碼。寫註解是一個好習慣,所以最好養成這個好習慣,把所有的程式碼都寫上註解字串。

執行函式的時候會產生一個目前 (local) 的符號表 (system table),這個表是用來記錄函式中的所有 local 的變數的。更精確的來說,所有在函式中變數的設定值都會紀錄在這個system table中,所以當你要使用(reference)一個變數時,會先檢查 local 的 system table,然後是整個程式 (global) 的 system talbe,然後是內建的變數名稱。雖然 global 變數可以在函式使用(reference),但是不能在函式之內直接的設定其值(除非是在一個 global 的 statement 中建立的)。

當函式被呼叫時,實際傳入的函式參數是會被紀錄在被呼叫函式的 local system table 裡的。因此,參數被傳入時是 以其值傳入的 (call by value) 。在此的值指的是物件的參考( reference ),而非物件本身的 值。 4.1 當一個函式呼叫另一個函式時,就會因此呼叫而建立一個新的 local system table。

當定義函式的時候,也就在目前所在的 system table 裡定義了這個函式的名稱。對直譯器來說,這個函式名稱的資料型態是一個使用者自訂的函式。這個函式的值名稱可以被設定給另一個名稱,然後這個新的名稱就可以被當作是一個函式名稱來使用。這個過程就是一個一般的重新命名的機制。

>>> fib
<function object at 10042ed0>
>>> f = fib
>>> f(100)
1 1 2 3 5 8 13 21 34 55 89

你也許認為 fib 不是一個函式(function)而是一個程序(procedure)。如同在C中一樣,在 Python 的 procedure 指的是沒有傳回值的函式(function)。事實上,就技術上而言,procedure 也是有傳回值的,只是所傳回的是一個 Python 系統內鍵的值,叫做 None 。通常來說,如果只傳回 None 的話,直譯器不會印出這一個傳回值。但是,如果你真想看一看它的話,你可以這樣做:

>>> print fib(0)
None

如果想讓你的函式傳回一個包含費氏數列的 list,而不是只印出來的話,其實是很簡單的:

>>> def fib2(n): # return Fibonacci series up to n
… “Return a list containing the Fibonacci series up to n”
… result = []
… a, b = 0, 1
… while b < n:
… result.append(b) # see below
… a, b = b, a+b
… return result

>>> f100 = fib2(100) # call it
>>> f100 # write the result
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

如同往例,這個例子事實上示範了一些新的Python的特點:

return 敘述使得函式傳回了一個值。如果單單 return 沒有其他的 expression 來表示傳回的值時,就表示這是一個從 procedure 傳回來的寫法(procedure到結束都沒有傳回值也是表示從 procedure 傳回來)。這種寫法表示傳回值是 None 。

result.append(b) 這個敘述表示呼叫了 result 這個list物件的一個方法( method ) 。Method 是一個特別”屬於”某個物件的函式,而且其名稱的形式是 obj.methodname 。在這裡 obj 指的是某一個物件(我們也可以用 expression 來代替),而 methodname 指得是由這個物件的資料型態所定義的這個方法的名稱。不同的資料型態會定義不同的方法,不同的資料型態也許所定義的方法名稱會相同,但是並不會造成衝突(你可以定義你自己的資料型態及其方法,我們稱之為類別( classes ),後面會再談到的)。在這個例子裡的 append() 方法是在 list 這個資料型態中定義的。這個方法會在 list 的最後面加入一個新的成員,在這個例子裡也可以寫作 ” result = result + [b]” ,效果一樣,但是用方法來寫有效率多了。

///// 定義函式(續)

在定義函式的時候我們可以加入不定數目的參數,加入參數的寫法有三種,是可以混和使用的。

最好用的一種寫法是,對其中的一個或多個參數給它一個特定的預設值。這樣子的話,當你在呼叫函式時,就可以不用傳入參數,或是傳入較少的參數了。請看下例:

def ask_ok(prompt, retries=4, complaint=’Yes or no, please!’):
while 1:
ok = raw_input(prompt)
if ok in (‘y’, ‘ye’, ‘yes’): return 1
if ok in (‘n’, ‘no’, ‘nop’, ‘nope’): return 0
retries = retries – 1
if retries < 0: raise IOError, ‘refusenik user’
print complaint

當你呼叫這個函式的時候你可以用 ask_ok(‘Do you really want to quit?’) ,或者是 ask_ok(‘OK to overwrite the file?’, 2) 。

設定的預設值可以是一個變數,但是這個變數在函式定義的時候就以定義時的情況( defining scope )決定(evaluate)了其值,所以以下的例子:

i = 5
def f(arg = i): print arg
i = 6
f()

印出的結果會是 5 。

重要的警告: 這個參數預設值只有被 evaluate 一次,這在當預設值是可變的物件像是 list 或是 dictionary 時會造成重要的差別。舉例來說,底下的函式會記錄曾經被呼叫過每次所傳入的參數。

def f(a, l = []):
l.append(a)
return l
print f(1)
print f(2)
print f(3)

印出來的結果會是:

[1]
[1, 2]
[1, 2, 3]

所以如果你的預設值是一個可變的物件,但是你又不想讓每次呼叫都共用的時候,你就必須如此寫你的函式:

def f(a, l = None):
if l is None:
l = []
l.append(a)
return l

///// 關鍵字參數

呼叫函式時也可以使用關鍵字參數,其形式是 “keyword = value” ,底下的這個函式:

def parrot(voltage, state=’a stiff’, action=’voom’, type=’Norwegian Blue’):
print “– This parrot wouldn’t”, action,
print “if you put”, voltage, “Volts through it.”
print “– Lovely plumage, the”, type
print “– It’s”, state, “!”

用這些方式呼叫都是正確的:

parrot(1000)
parrot(action = ‘VOOOOOM’, voltage = 1000000)
parrot(‘a thousand’, state = ‘pushing up the daisies’)
parrot(‘a million’, ‘bereft of life’, ‘jump’)

但是用這些方式都是不正確的:

parrot() # required argument missing
parrot(voltage=5.0, ‘dead’) # non-keyword argument following keyword
parrot(110, voltage=220) # duplicate value for argument
parrot(actor=’John Cleese’) # unknown keyword

一般來說,一連串的參數的次序是先有非關鍵字參數(也可以沒有)然後才是關鍵字參數,關鍵字必須是函式定義時所用的參數名稱。這個定義時用的參數名稱有沒有預設值並不重要,但是一個傳入的參數只能有一個值(預設值不算),如果你已經先用非關鍵字參數給了某個參數一個值,接下來你就不能再用關鍵字參數給它另外的值。底下的例子就違反了這個規則:

>>> def function(a):
… pass

>>> function(0, a=0)
Traceback (innermost last):
File “<stdin>”, line 1, in ?
TypeError: keyword parameter redefined

當一個函式定義時參數名稱是以 ** name 這種形式定義時,表示這個參數要接受的是一個 dictionary (譯:字典,包含許多關鍵字以及值的對應),這個 dictionary 包含許多的關鍵字參數,但是這些關鍵字不能跟其他的參數名稱相同。另外參數也可以用 *name 這種形式定義(下一小節會解釋),這種方式定義的參數要接受的是一個 tuple (譯:不可更動的 list),這個 tuple 可以接受不限數目的非關鍵字參數( *name 必須要出現在 **name 之前)。下面的例子就是一個函式定義的範例:

def cheeseshop(kind, *arguments, **keywords):
print “– Do you have any”, kind, ‘?’
print “– I’m sorry, we’re all out of”, kind
for arg in arguments: print arg
print ‘-‘*40
for kw in keywords.keys(): print kw, ‘:’, keywords[kw]

要呼叫這個函式,你可以這樣呼叫:

cheeseshop(‘Limburger’, “It’s very runny, sir.”,
“It’s really very, VERY runny, sir.”,
client=’John Cleese’,
shopkeeper=’Michael Palin’,
sketch=’Cheese Shop Sketch’)

函式執行的結果如下:

— Do you have any Limburger ?
— I’m sorry, we’re all out of Limburger
It’s very runny, sir.
It’s really very, VERY runny, sir.
—————————————-
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch

///// 隨意的參數串

最後,我們要介紹最不常見的形式,也就是定義一個函式可以接受任意數目的參數,這些傳入的參數會被放進一個 tuple 裡面去。在這一個任意數目的參數之前,可以定義沒有或是一個或是多個普通的參數:

def fprintf(file, format, *args):
file.write(format % args)

///// Lambda 形式

由於眾多的需求,Python 裡面也加入了這一個在其他功能性程式語言及 Lisp 裡面常見的特性。你可以使用 lambda 這個關鍵字來定義一些小的沒有名字的函式。底下是一個傳回兩個參數值相加結果的例子: “lambda a, b: a+b” 。Lambda 形式可以使用在任何需要函式物件(function objects)的地方。語法上限制 lambda 形式只能有一個 expression,其功能只是方便的取代一個正常的函式定義。就像是函式裡面包含函式定義一樣,lambda形式不能使用(reference)外面一層函式的的變數,但是你可以使用傳入預設值參數的方式來克服這個問題,像是下面的例子:

def make_incrementor(n):
return lambda x, incr=n: x+incr

///// 註解字串

註解字串的內容及形式是有一個新的約定俗成的規範的。

第一行應該是一個有關這個物件的目的的短的、簡潔的摘要。因為簡潔的緣故,這一行不應該包括物件的名稱及型態(除非物件的的名稱正好是解釋物件目的的一個動詞),因為物件名稱及型態是可以從其他地方得知的。這一行第一個字的第一個字母應該大寫,最後應該有一個句點。

如果註解字串還包含其他行的話,第二行應該是空白的,這樣可以讓摘要及細部的解釋有所區分。底下的各行應該是一個或多個段落,其目的應該是諸如解釋物件的呼叫方法及其副效果(side effects)的解釋說明。

一般時候,Python 的分析器(parser)並不會把多行字串的縮排拿掉,但是在註解字串中,註解字串的處理工具需要特別拿掉那些縮排。底下的一般通用準則可以用來幫助決定註解字串如何縮排:在第一行之後所遇到的第一個非空白行決定了整個註解字串的縮排大小,(我們不能用第一行,因為第一行一定要跟著引號在一起,所以其縮排是不明顯的)。在這之後的與這個縮排相等的空白,都會被整個拿掉。如果某行的前面有空白但縮排的空白不足(這是不應該發生的),這些縮排也會被整個拿掉。空白的解釋是把tab展開後(一般為八個空白)的方式來解釋的。

這裡示範了如何使用多行的註解字串:

>>> def my_function():
… “””Do nothing, but document it.

… No, really, it doesn’t do anything.
… “””
… pass

>>> print my_function.__doc__
Do nothing, but document it.

No, really, it doesn’t do anything.

///// 列(Lists)(續)

列(list)這個資料型態有一些方法可以使用,底下我們就列出來一些常用的方法:

append(x)
在 list 的尾端加入一個成員,也可以用這個方法來寫 a[len(a):] = [x] 。

extend(L)
接受一個新的 list 的參數,然後把它加入到目前這個 list 的尾端,也可以寫作 a[len(a):] = L 。

insert(i, x)
在某個特定的位置加入一個成員。第一個參數是要加入的位置的 index,所以 a.insert(0, x) 會加入在 list 的最前端,而 a.insert(len(a), x) 會在最後端加入,相等於 a.append(x) 。

remove(x)
拿掉第一個其值相等於 x. 的成員。如果整個 list 都沒有這個成員,那就會得到一個錯誤(error)。

pop([i])
從一個 list 中拿掉某個位置的成員,並且傳回這個被拿掉的成員。如果沒有傳入位置的 index 的話, a.pop() 會傳回這個 list 的最一個成員,同樣的這個成為會被從這個 list 之中拿掉。

index(x)
傳回第一個其值相等於 x 的成員之位置(index),如果整個 list 都沒有這個成員,那就會得到一個錯誤(error)。

count(x)
傳回在整個list裡面, x 出現了多少次。

sort()
針對 list 裡面的成員做排序。

reverse()
反轉整個 list 裡面成員的位置。

底下的這個例子使用了大部分的 lsit 的方法(method):

>>> a = [66.6, 333, 333, 1, 1234.5]
>>> print a.count(333), a.count(66.6), a.count(‘x’)
2 1 0
>>> a.insert(2, -1)
>>> a.append(333)
>>> a
[66.6, 333, -1, 333, 1, 1234.5, 333]
>>> a.index(333)
1
>>> a.remove(333)
>>> a
[66.6, -1, 333, 1, 1234.5, 333]
>>> a.reverse()
>>> a
[333, 1234.5, 1, 333, -1, 66.6]
>>> a.sort()
>>> a
[-1, 1, 66.6, 333, 333, 1234.5]

///// 把列(Lists)當作堆積(Stacks)使用

由於有這些好用的方法,把列(list)當作堆積(stack)來使用是一件容易的事。Stacks 的最後一個加入的成員是第一個被取出來的成員(後進先出“last-in, first-out”法則)。要在stack的最頂端加入一個成員可以使用 append() ,要從 stack 的最頂端取出一個成員可以用 pop() (不須加入參數)。例子如下:

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]

///// 把列(Lists)當作佇列(Queues)使用

你也可以很方便的拿list來當作佇列(queues)來使用。Queues的特點是第一個加入的成員會是第一個被取出的成員(先進先出“first-in, first-out”法則)。要在queue的後端加入一個成員可以使用 append() ,要從queue的最前端取出一個成員可以使用 use pop() ,記得參數是 0 。例子如下:

>>> queue = [“Eric”, “John”, “Michael”]
>>> queue.append(“Terry”) # Terry arrives
>>> queue.append(“Graham”) # Graham arrives
>>> queue.pop(0)
‘Eric’
>>> queue.pop(0)
‘John’
>>> queue
[‘Michael’, ‘Terry’, ‘Graham’]

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.