第四章 流程控制

前面我们已经见到了如何由用while结构控制流程运行。这一章我们介绍更多的控制 结构。Python具有和其它语言类似的控制结构但略有差别。

4.1 If 语句

If 语句可能是最基本的程序分支语句了。例如:

>>> 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的缩写,这样可以缩短 语句行长度。其它语言中switch 或 case 语句可以用if...elif...elif...语句组来实现。

4.2 for 语句

Python中的for语句与你可能熟悉的C或者Pascal中的相应语句略有不同。不象Pascal 那样总是对数字序列进行循环,也不是象C中那样完全由程序员自由地控制循环条件和循环体 ,Python的for循环是对任意种类的序列(如列表或字符串)按出现次序遍历每一项。例如:

>>> # 计算字符串长:
... a = ['cat', 'window', 'defenestrate']
>>> for x in a:
...     print x, len(x)
... 
cat 3
window 6
defenestrate 12
>>>

尽量不要在循环体内修改用来控制循环的序列(当然,只有可变的序列类型如列表才有可 能被修改),这样程序可能会出问题。如果需要这样,比如说要复制某些项,可以用序列的 副本来控制循环。片段记号让你很容易生成副本:

>>> for x in a[:]: # 生成整个列表的片段副本
...    if len(x) > 6: a.insert(0, x)
... 
>>> a
['defenestrate', 'cat', 'window', 'defenestrate']
>>>

结果是把列表中长度超过6个字符的字符串插入到列表开头。

4.3 range() 函数

如果确实需要对一列数字进行循环的话,可以使用内置函数range()。它生成包含数字序 列的列表,如:

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

注意给出的终点永远不出现在生成的列表中,range(10)生成一个十个数的列表,恰好是 长度为10的序列的合法下标的各个值。也可以指定不同的起始点,或者指定不同的间隔(甚 至负数):

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

为了对序列的下标进行循环,如下联合使用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 break语句,continue语句和循环中的else子句

如同C语言一样,break语句跳出其所处的最内层的for 或 while循环,continue语句继续 下一循环步。

循环语句还可以带一个 else 子句,当循环正常结束时执行其内容,但如果循环是用break 语句跳出的则不执行其内容。下例说明了这种用法,此例求素数:

>>> 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 # 等待键盘中断
...
 

4.6 函数定义

我们可以定义一个函数用来计算某一界限以下的所有Fibonacci序列值:

>>> def fib(n):    # 写出 n 以下的所有Fibonacci序列值
...     a, b = 0, 1
...     while b < n:
...           print b,
...           a, b = b, a+b
... 
>>> # 调用刚刚定义的函数:
... fib(2000)
 
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
>>>

其中关键字 def 开始一个函数定义,其后应该是函数名,括号内的形参表,以冒号结束 。构成函数体的各语句从下一行开始,用一个制表符缩进。函数的第一个语句可以是一个字 符串,如果是的话,这个字符串就是函数的文档字符串,简称为docstring。有一些工具可以 利用文档字符串自动生成可打印的文档,或者让用户交互地浏览代码,所以在自己编程时加 入文档字符串是一个好习惯,应该养成这样的习惯。

函数在执行时对局部变量引入一个新的符号表。函数中的变量赋值都存入局部符号表;引 用变量时变量名先从局部符号表中查找,然后在全局符号表中查找,最后从内置的名字中查 找。因此,在函数中不能直接对全局变量赋值(除非用了global语句来说明),但可以引用 全局变量的值。

函数调用的实参被引入函数的局部符号表,即函数的参数是按值调用的。函数再调用其它 函数时为该函数生成一个新的符号表。但是严格地说,函数的调用是按引用调用的,因为如 果参数是一个可变类型如列表的话在函数中改变形参的内容将导致实参的内容被改变(不改 变的是实参名字的绑定关系)。

函数定义把函数名放入当前符号表。函数名的值类型为用户自定义函数,这个值可以赋给 另一个名字,从而这个名字也代表相同的函数。这可以作为一般的改名方法:

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

你可能会说 fib 不是函数而是过程。Python和C一样,过程只是不返回值的函数。实际上 ,严格地说,过程也返回一个值,只不过是一个很没意思的值。这个值叫做 None(这是一个 内置的名字)。解释程序交互运行时如果只需要显示这个值的话就会忽略不显示。如果希望 显示的话可以用 print 语句:

 
>>> print fib(0)
None
>>>
 
也可以写一个函数返回Fibonacci 序列的数值列表而不是显示这些值:
 
>>> def fib2(n): # 返回直到n的Fibonacci 序列值
...     result = []
...     a, b = 0, 1
...     while b < n:
...           result.append(b)    # 解释见下面
...           a, b = b, a+b
...     return result
... 
>>> f100 = fib2(100)    # 调用
>>> f100                # 输出结果
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
 
>>>
 

这个例子也演示了新的Python特色:return语句从函数中退出并返回一个值。不带返回值 的return可以从过程中间退出,运行到过程的末尾也可以退出,这两种情况下返回None。

语句result.append(b)调用列表对象result的一个方法。方法是“属于”一个对象的函数 ,引用格式为obj.methodname,其中obj是某个对象(也允许是一个表达式), methodname 是由该对象的类型定义的一个方法的名字。不同的不同的方法。不同类型的方法可以使用相 同的名字而不致引起误解。(可以定义自己的对象类型和方法,使用类,本文后面会讨论这 个话题)。例子中的append()方法时列表对象的方法,它在列表末尾增加一个新元素。在本 例中这等价于“result = result + [b]”,只是更有效。

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)。缺省值是在函数定义时的定义作 用域中计算的,所以例如:

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

将显示5。

注意:缺省值只计算一次。当缺省值是可变对象如列表或字典时这一点是要注意的。例如 ,以下函数会在以后的调用中累加它的值:

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

如果你不希望缺省值在连续的调用中被保留,可以象下面这样改写函数:

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

4.7.2 关键字参数

函数调用时也可以象“关键字 = 值”这样指定实参,其中关键字是定义时使用的形参的 名字。例如:

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()                     # 非缺省的参数没有提供
parrot(voltage=5.0, 'dead')  # 关键字参数后面又出现了非关键字参数
parrot(110, voltage=220)     # 参数值重复提供
parrot(actor='John Cleese')  # 未知关键字

一般说来,实参表中位置参数在前,关键字参数在后,关键字名字必须是形参名字。形参 有没有缺省值都可以用关键字参数的形式调用。每一形参至多只能对应一个实参,因此,已 经由位置参数传入值的形参就不能在同一调用中再作为关键字参数。

如果形参表中有一个形为**name的形参,在调用时这个形参可以接收一个字典,字典中包 含所有不与任何形参匹配的关键字参数。形参表中还可以使用一个特殊的如*name的形参,它 将接受所有不能匹配的位置参数组成的一个序表。*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 任意个数参数

在所有有名的形参的后面可以有两个特殊的形参,一个以*args的形式命名,一个以**kw 的形式命名。有了*args形式的形参后函数在调用时就可以在正常的能匹配的实参表后面输入 任意个数的参数,这些参数组成一个序表赋给args形参,不能匹配的关键字参数组成一个字 典赋给kw形参。在任意个数形参之前可以有0到多个正常的参数。例如:

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

4.7.4 Lambda形式

因为许多人的要求,Python中加入了一些在函数编程语言和Lisp中常见的功能。可以用lambda 关键字来定义小的无名函数。这是一个返回其两个参数的和的函数:“lambda a, b: a+b” 。Lambda形式可以用于任何需要函数对象的地方。从句法上讲lambda形式局限于一个表达式 。从语义上讲,这只是正常的函数定义的句法甜食。像嵌套函数定义一样,lambda形式不能 访问包含其定义的作用域中的变量,但审慎地使用缺省参数之可以绕过这个限制。例如:

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

4.7.5 文档字符串

关于文档字符串的内容与格式正在形成一些惯例。第一行应该为简短的对象目的概括说明 。为了简明起见,这一行不应该提及对象的名字或类型,因为这些可以通过其他途径得知( 当然如果对象名字就是一个描述函数操作的动词则当然可以提及其名字)。着以行应该用大 些字母开始,以句点结尾。如果文档字符串中有多行,第二行应该是空行,把概括说明与其 它说明分开。以下的行可以是一段或几段,描述对象的调用方法,它的副作用,等等。

Python的扫描程序不会从多行字符串中去掉缩进空白,所以处理文档的工具需要自己处理 缩进。只要遵循如下的惯例就可以有利于缩进空白的处理。在第一行之后的第一个非空白的 行决定整个文档字符串的缩进数量(我们不用第一行,因为它经常是直接跟在表示字符串开 始的引号后面)。文档字符串中除第一行以外的各行都要删除等价于此行的缩进量的空白。 对制表符将扩展为空格后再删除。