小段落


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之中插入一个物件)。

请看 关于此文件… 里面有关如何给我们建议的说明。