第4章 函数

4.1 函数调用

编程中的函数是执行计算的一个命名语句序列。当定义函数时,需要指定函数名和语句顺序。定义好之后,通过函数名就可以调用函数。之前我们见到过一个函数示例:

>>> type(32)
<type 'int'>

这个函数的名称是type 。括号里面的表达式称为函数的参数。参数可以是一个值或变量,作为函数的输入。type函数的作用是显示参数的类型。

一般而言,函数获取参数,然后返回结果。这个结果称为返回值。

4.2 内置函数

Python提供了很多重要的内置函数,不需要我们预先定义就可以使用。Python开发者定义了解决常见问题的函数集,内置在Python中,供我们使用。

max函数和min函数分别求得一个列表中的最大值和最小值:

>>> max('Hello world')
'w'
>>> min('Hello world')
' '
>>>

max函数告诉我们,这个字符串中最大的字符是(“w”),min函数告诉我们这个字符串中最小的字符是空格符。

另一个常用的内置函数是len函数,它能返回参数的长度。如果len函数的参数是一个字符串,那么它返回的是这个字符串的字符数。

>>> len('Hello world')
11
>>>

这些函数不仅可用于字符串,还能操作其他数据类型,后续章节会涉及到。

你可以将内置函数类比为保留字,举例来说,不要使用“max”作为变量名。

4.3 类型转换函数

Python提供了将一种类型转换成另一种类型的内置函数。在允许的情况下,int函数能把任何其他类型的值转化成整数型,如果不能转换就会报错:

>>> int('32')
32
>>> int('Hello')
ValueError: invalid literal for int(): Hello

int函数能将浮点型转换成整数型,但是它不进行四舍五入,只是直接去掉小数部分:

>>> int(3.99999)
3
>>> int(-2.3)
-2

float函数能把整数型和字符串型转换成浮点型:

>>> float(32)
32.0
>>> float('3.14159')
3.14159

str函数能把传入的参数转换成字符串型:

>>> str(32)
'32'
>>> str(3.14159)
'3.14159'

4.4 随机数

既定输入的情况下,大部分的计算机程序每次都会产生相同的输出,也就是说这是板上钉钉的事情。一般而言,确定性是好的,我们希望同一个算法产生相同的结果。然而,有些时候我们希望计算机具备不确定性,比如游戏就是一个典型例子,当然还有很多其他例子。

让一个程序完全处于不确定状态,这很难做到。但是,有一些方法可以让它看起来具有不确定性。一种方法是利用算法产生伪随机数。伪随机数并不是真正的随机数,原因在于它们是利用确定的算法计算出来的。单看这些数字并不会发现它们和真正的随机数有什么不同。

随机数模块提供了伪随机数生成的函数,以下简称“random”。

random函数返回一个介于0.0-1.0的随机浮点数(包括0.0,但不包括1.0)。每次调用时,你会得到一长串的数字。举例来说,执行下面的循环语句:

import random

for i in range(10):
    x = random.random()
    print x

程序生成了10个介于0.0-1.0之间但不包括1.0的随机数。

0.301927091705
0.513787075867
0.319470430881
0.285145917252
0.839069045123
0.322027080731
0.550722110248
0.366591677812
0.396981483964
0.838116437404

习题4.1 运行以下程序并观察得到的结果,不妨多运行几次看看。

random函数只是众多随机数函数中的一个。randint函数传入两个整数参数,并返回介于这两个参数之间的整数(包括这两个参数在内)。

>>> random.randint(5, 10)
5
>>> random.randint(5, 10)
9

为取得一组值中的随机元素,可以使用下面的程序:

>>> t = [1, 2, 3]
>>> random.choice(t)
2
>>> random.choice(t)
3

random模块还提供了从连续分布函数中提取随机值的函数,包括高斯函数、指数函数、伽玛函数等。

4.5 数学函数

Python的math模块提供了很多常见的数学函数。在使用之前要导入相应的包:

>>> import math

这条语句创建了一个名为math的模块对象。如果将这个模块对象打印出来,就会得到关于它的一些信息:

>>> print math
<module 'math' from '/usr/lib/python2.5/lib-dynload/math.so'>

这个模块对象包括了模块中定义的函数和变量。为了使用其中的函数,你必须指定模块名和函数名,用圆点隔开,也就是英文的句号。这种格式叫做点标记(dot notation)。

>>> ratio = signal_power / noise_power
>>> decibels = 10 * math.log10(ratio)

>>> radians = 0.7
>>> height = math.sin(radians)

第一个例子是计算以10为底的信噪比的对数。math模块还提供了log函数用来求以e为底的对数。

第二个例子调用了正弦函数。变量名字给出了提示,除了正弦函数,还有余弦,正切等其他三函数都采用弧度作为参数。将度数转换成弧度的运算是除以360,然后乘以2π:

>>> degrees = 45
>>> radians = degrees / 360.0 * 2 * math.pi
>>> math.sin(radians)
0.707106781187

math.pi表达式从math模块中取得变量pi的值。这个变量是π的一个近似值,保留了小数点后15位数。

运用你的平面几何知识,与2的平方根再除以2的结果进行比较,检查程序输出是否正确。

>>> math.sqrt(2) / 2.0
0.707106781187

4.6 定义新函数

目前为止,我们只是使用了Python的内置函数,除此之外也可以自定义新函数。通过定义函数名和一组语句序列来定义一个新函数,然后在执行时调用这个函数。一旦定义了一个函数,程序中可以重复使用。

def print_lyrics():
    print "I'm a lumberjack, and I'm okay."
    print 'I sleep all night and I work all day.'

def是用来定义函数的保留关键字。这个函数的名称是print_lyrics。函数命名与变量命名的规则基本上是一样的。字母、数字以及一些符号是合法的,但是函数名的第一个字符不能是数字。不能使用保留关键字命名函数,也要避免函数名和变量名相同。

函数名后面的空括号表明这个函数没有指定参数。接下来,我们会定义有参数的函数。

函数定义的第一行叫做函数头,剩余的部分叫做函数体。函数头必须以冒号结束,函数体必须缩进。按照惯例,一般缩进4个空格。函数体可以包括任意数量的语句。

print语句要输出的字符串放在双引号里面。单引号和双引号作用是一样的。除了在字符串中也有单引号的冲突情况之外,大多数人会使用单引号。

如果你在Python交互模式中定义函数,解释器会打印省略号(…)表明函数定义还没有结束。

>>> def print_lyrics():
...     print "I'm a lumberjack, and I'm okay."
...     print 'I sleep all night and I work all day.'
...

函数定义的结束方式是输入一个空行(当然,这在程序文件中不是必须的)。

定义函数的同时也会创建一个和函数名相同的变量名。

>>> print print_lyrics
<function print_lyrics at 0xb7e99e9c>
>>> print type(print_lyrics)
<type 'function'>

print_lyrics的值是一个函数对象,它的类型是函数。

调用自定义函数与调用内置函数的语法是一样的:

>>> print_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

函数定义好之后,就可以在其他函数中使用。例如,为了重复调用之前的函数,你可以写一个repeat_lyrics函数:

def repeat_lyrics():
    print_lyrics()
    print_lyrics()

然后调用这个函数:

>>> repeat_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

现实中歌曲可不是这样填词的。

4.7 定义与用法

将前面的代码片段组合在一起,完整的程序如下:

def print_lyrics():
    print "I'm a lumberjack, and I'm okay."
    print 'I sleep all night and I work all day.'

def repeat_lyrics():
    print_lyrics()
    print_lyrics()

repeat_lyrics()

这个程序包含了两个自定义函数:print_lyrics 和 repeat_lyrics。函数定义会像其他语句一样执行,但它的作用仅仅是新建函数。函数体里面的代码并没有被执行,直到被调用时才会执行,函数定义是没有输出的。

正如所期望的,你必须在执行函数之前定义这个函数。换句话说,函数在第一次被调用之前就应该定义好。

习题4.2 将上面程序的最后一行调到最开始,这样函数调用就出现在函数定义之前。运行程序并观察出错信息。

习题4.3 将函数调回底部,将函数print_lyrics定义放在函数repeat_lyrics后面,这样会产生什么结果?

4.8 执行流程

为了保证函数定义是在第一次使用之前,你就必须知道语句的执行顺序,这称为执行流程。

程序一般从第一条语句开始执行,从上到下依次顺序执行。

函数定义并不改变程序的执行顺序,但要记住,函数只有被调用的时才会被执行。

函数调用像是是执行流程中的一个迂回。当遇到要调用的函数时,不会接着去执行下一句,而是跳到函数体,执行完函数体里的语句,然后回到刚才跳出的地方继续执行。

当你掌握了函数之间的调用,这就会变得很简单。在一个函数中,程序可能需要执行另一个函数的语句。还有,在定义新函数时,程序还可能需要执行另一个函数!

程序里跳来跳去,这说明了什么?阅读程序有时候没必要从头看到尾。有时候按照执行流程会让你更好地读懂程序。

4.9 形式参数与实际参数

我们之前见到的内置函数需要传入参数。例如,调用math.sin函数需要传入一个数值作为参数。有些函数不止有一个参数:math.pow函数需要传入两个参数:底数和指数。

在函数内部,如果变量作为参数传入,那么这种参数叫做形式参数(简称形参)。下面的例子是一个自定义函数传入了一个实际参数(简称实参):

def print_twice(bruce):
    print bruce
    print bruce

这个函数将一个叫做bruce的实参传递进去。当该函数被调用时,函数会打印两次传入的参数值。

这个函数适用任何类型的值。

>>> print_twice('Spam')
Spam
Spam
>>> print_twice(17)
17
17
>>> print_twice(math.pi)
3.14159265359
3.14159265359

这种参数传入方式适用于内置函数与自定义函数。任何类型的表达式都可以作为函数print_twice的参数。

>>> print_twice('Spam '*4)
Spam Spam Spam Spam
Spam Spam Spam Spam
>>> print_twice(math.cos(math.pi))
-1.0
-1.0

参数的值在函数被调用之前就已经被计算出来了,因此,上面的例子中表达式'Spam '*4 和 math.cos(math.pi)只被计算了一次。

你还可以使用变量作为参数:

>> michael = 'Eric, the half a bee.'
>>> print_twice(michael)
Eric, the half a bee.
Eric, the half a bee.

变量(如michael)作为形参与实参(如bruce)不同。函数的形式参数是什么类型的值都可以被传入,而实参的值是确定的,print_twice函数的返回值就是参数bruce本身。

4.10 带返回值的函数和空函数

我们使用过的有些函数(如math函数)有返回值。由于没有更好的叫法,我称之为有返回值的函数。其他的一些函数比如print_twice,执行任务但不返回值,这种称为空函数。

当调用有返回值的函数时,大部分时候你想要利用返回值,例如,将它赋值给一个变量,也可以作为表达式的一部分:

x = math.cos(radians)
golden = (math.sqrt(5) + 1) / 2

在交互模式中调用函数时,Python会显示结果。

>>> math.sqrt(5)
2.2360679774997898

在程序文件中,如果不把有返回值的函数的返回值保存在变量里的话,返回值就会丢失。

math.sqrt(5)

空函数也许会在屏幕上输出什么或者有其它的用途,但其本身没有返回值。如果你试着将结果赋值给一个变量,就会得到一个特殊的值None。

>>> result = print_twice('Bing')
Bing
Bing
>>> print result
None

None值与字符串’None’是不一样的。它是一种特殊的值并且拥有自己的类型。

>>> print type(None)
<type 'NoneType'>

使用return语句从函数中返回结果。例如,创建一个简单的函数addtwo,将两个数相加并且返回一个值。

def addtwo(a, b):
    added = a + b
    return added

x = addtwo(3, 5)
print x

在文件中执行程序时,print语句会打印出8。addtwo函数被调用时,传入3和5这两个参数。带有形参a和b的函数,分别是3和5。这个函数将两个数相加,然后将结果赋值给局部变量added,最后使用return语句将计算结果赋值给x并打印出来。

4.11 为什么需要函数?

如果不太明白为什么需要花费精力把程序分成多个函数,请看如下理由:

  • 定义新函数可以让你有机会为一组语句命名,使得程序易于阅读、理解和调试。
  • 函数可以消除冗余代码,为程序瘦身。如果想修改函数,只需要改变一处即可。
  • 将长程序分割成多个函数,逐一调试,最后再整合在一起。
  • 精心设计的函数对很多程序而言都会有用。一旦创建并调试后,你就可以重复使用它。

本书其余部分会使用函数定义来解释一个概念。创建并正确使用函数的技巧之一是捕捉到一个想法,例如,“找出列表中的最小值”。随后我们会介绍min函数的代码,它将列表的值作为参数输入,然后返回其中的最小值。

4.12 调试

制表符和空格通常不可见,导致不易调试,因此最好选择一个能够自动缩进的文本编辑器。

另外,运行程序之前不要忘记保存。有一些开发环境可以自动保存,而有一些不行。未保存的情况下运行的程序和文本编辑器的程序不一样。

如果你重复运行相同错误的程序,那么会调试很久。

确保运行的代码是文本编辑器中你所看到的代码。如果不确定,你可以在程序开头加入print 'hello'这样的代码再运行。如果没有看到hello输出,那么就没有正确运行程序。

4.13 术语

算法:解决一类问题的一般过程。

实际参数:函数被调用时传入的值。函数中这个值会被赋予相应的参数。

函数体:函数定义中的一组语句序列。

组合:一个表达式作为另一个表达式的一部分,或者一条语句作为另一条语句的一部分。

确定性:在既定输入下,程序每次运行都一样并返回相同的结果。

点标记:在另一个模块中调用函数时,需要指定模块名和函数名,用句号隔开。

执行流程:程序运行时的语句执行顺序。

带返回值的函数:能够返回一个值的函数。

函数:执行一些有用操作的命名语句序列。函数不一定有参数,也不一定返回结果。

函数调用:执行函数的语句,包括函数名和参数列表。

函数定义:创建新函数的语句,需要指定名称、参数和执行语句。

函数对象:通过函数定义创建的值。函数名就是参引函数对象的变量。

函数头部:函数定义的第一行。

import语句:读取模块文件并创建模块对象的语句。

模块对象:import语句创建的一个值,用以提供模块中定义的数据和代码。

形式参数:函数中用来参引传入参数的变量名。

伪随机数:看起来像随机数的一串数值,不过这是由确定的程序产生的。

返回值:函数的结果。如果一个函数调用作为表达式来使用,那么返回值就是这个表达式的值。

空函数:没有返回值的函数。

4.14 练习

习题4.4 Python中”def”保留关键字的作用是什么?

a) 它是一个俚语,“下面的代码太酷了”

b) 它表示函数的开始

c) 它表示接下来代码的缩进部分会被存储

d) b和c都正确

e) 以上都不正确

习题4.5 下面的Python语句会打印出什么?

def fred():
   print "Zap"

def jane():
   print "ABC"

jane()
fred()
jane()

a) Zap ABC jane fred jane

b) Zap ABC Zap

c) ABC Zap jane

d) ABC Zap ABC

e) Zap Zap Zap

习题4.6 重写之前的工资计算程序,加班时间的工资按原工资的150%计算。创建一个computepay函数,包含两个参数hours和rate。

Enter Hours: 45

Enter Rate: 10

Pay: 475.0

习题4.7 重写之前的分数计算程序,使用computegrade函数,score作为参数,返回一个评分等级的字符串。

Score   Grade
> 0.9     A
> 0.8     B
> 0.7     C
> 0.6     D
<= 0.6    F


Program Execution:

Enter score: 0.95
A

Enter score: perfect
Bad score

Enter score: 10.0
Bad score

Enter score: 0.75
C

Enter score: 0.5
F

重复运行该程序,每次输入不同的值来测试输出结果。