小段落


4. 更多流程控制的工具

除了我們剛剛介紹的 while 敘述之外,Python也能夠使用大部分其他程式語言使用的流程控制形式 ─ 除了有一些不同之外。


4.1 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 的寫法。


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


4.3 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


4.4 breakcontinue 敘述,以及在迴圈中的 else 子句

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

同樣的,從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


4.5 pass 敘述

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

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


4.6 定義函式

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

>>> 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的特點:


4.7 定義函式(續)

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


4.7.1 預設內定參數值

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

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


4.7.2 關鍵字參數

呼叫函式時也可以使用關鍵字參數,其形式是 " 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


4.7.3 隨意的參數串

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

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


4.7.4 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


4.7.5 註解字串

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

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

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



註腳

... 物件本身的值。4.1
事實上是,較洽當的說法是以其物件參考傳入的( call by object reference ),因為如果一個不可改變的物件傳入之後,呼叫這個函式的地方仍然可以看到這個函式對這個物件的改變(例如在list之中插入一個物件)。

請看 關於此文件… 裡面有關如何給我們建議的說明。