第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
重复运行该程序,每次输入不同的值来测试输出结果。