[阅读]Think Python - CH3 Functions
调用函数
在程序中,函数是一段被命名的语句,进行某个运算。当你定义一个函数时,需要指明函数名和语句。然后,可以通过函数名调用函数。我们已经见过函数调用了:
>>> type(32) <type 'int'>
函数名为type。括号内的表达式叫做函数的参数。这个函数的结果,是返回参数的类型。
一般来说,函数都需要获得参数,并返回结果。结果被叫做返回值。
类型转换函数
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'
数学函数
Python有一个数学模块,他提供了大部分常见的数学函数。模块是一个包含相关函数的文件。
在使用之前,必须先加载它:
>>>import math
上面的语句创建一个名叫math的模块对象。如果你print 模块对象,可以得到一些相关信息:
>>> print math <module 'math' (built-in)>
模块对象包含其中定义的函数和变量。使用模块中的函数,必须写出模块名称和函数名称,它们中间以点隔开。这种格式叫做圆点记法。
>>> ratio = signal_power / noise_power >>> decibels = 10 * math.log10(ratio) >>> radians = 0.7 >>> height = math.sin(radians)
第一个例子用log10计算分贝的信噪比(假设singal_power和noise_power已经定义)。math模块也提供了log,它计算以e为底的对数。
第二个例子是计算弧度的余弦值。参数变量名,说明了sin或者其他三角函数(cos,tan,等)的参数是弧度。除以360乘以2pi可以把角度转化为弧度:
>>> degrees = 45 >>> radians = degrees / 360.0 * 2 * math.pi >>> math.sin(radians) 0.707106781187
结构
目前为止,我们接触到的程序元素——变量、表达式和语句——都是孤立的,没有说如何将它们组合在一起。
程序语言一项重要的特性便是包括一些小的代码块并何以组合他们。例如,函数的参数可以是任何包括算术操作符的表达式:
x = math.sin(degrees / 360.0 * 2 * math.pi)
甚至是函数调用:
x = math.exp(math.log(x+1))
几乎所有需要一个值的地方都可以用表达式替代,除了赋值语句的左边,它必须是一个变量名。任何表达式放在左边都会导致语法错误(也有例外的情况,将在后面讲到)。
>>> minutes = hours * 60 # right >>> hours * 60 = minutes # wrong! SyntaxError: can't assign to operator
自定义函数
目前,我们只用到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_lyrice。函数名称的规则和变量名规则一样:字母、数字和一些符号是合法的,但是第一个字符不能是数字。你可以使用关键字作为函数名,但应该避免变量和函数使用同样的名字。
函数名后面的括号为空,表示该函数没有参数。
函数定义的第一样叫做头部;其余的部分叫做函数体。头部以冒号结尾,函数体必须缩进,缩进始终都是4个空格;见“调试部分”。函数体可以保护任意数量的语句。
print 语句中的字符串被双引号括住。单引号和双引号的作用一样,大部分情况下使用单引号,除非像上面这样,字符串中包含了单引号(也叫撇号)。
如果在交互环境中定义函数,解释器会输出省略号(...)提示,定义是否完成: //试了一下win7 2.7并没有...
>>> 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> >>> type(print_lyrics) <type 'function'>
print_lyrice的值是函数对象,它的类型是'function'。
调用自定义函数的语法和内建函数一样:
>>> print_lyrics() I'm a lumberjack, and I'm okay. I sleep all night and I work all day.
定义一个函数以后,可以在其他函数里面使用他,例如,重复上面的函数两次,我们可以写一个叫做repeat_lyrice的函数:
def repeat_lyrics(): print_lyrics() print_lyrics()
然后调用repeat_lyrice:
>>> 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.
可惜歌曲不是这么唱的。
定义和使用
将上面的几段代码放在一起,会成下面的样子:
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_lyrice 和 repeat_lyrice。定义函数再执行和其他代码一样,只是创建了一个函数对象。函数里面的语句不会执行,直到调用它,一般函数定义也没有输出。
如你所想,你必须在函数执行之前创建它。也就是说,函数定义的语句必须在首次函数调用之前执行。
练习 3-1
把上面程序的最后一行移动到第一行,这样函数调用出现在定义前面。运行程序,看看错误提示是什么。
//NameError: name 'repeat_lyrics' is not defined
练习 3-2
把函数调用语句放到最后,把print_lyrice定义放在repeat_lyrice后面,运行会发生什么。
//成功
运行流程
为了保证函数首次调用之前已经定义,你必须了解代码运行顺序,叫做,运行流程。
程序始终从代码的第一行开始执行。语句从上到下按顺序执行。
函数定义不会改变代码执行顺序,但是记住,函数定义中的代码不会执行,知道它被调用。
函数调用就想运行流程绕了弯。不是执行下一行代码,而是跳到函数体,执行函数体的所有代码,然后回到刚刚的地方。
这听起来很简单,但是一个函数调用另外一个就不简单了。在函数体中间,程序或许会执行另外一个程序中的代码。然而在执行这个函数的过程中,或许会再去执行另外一个函数的代码。
幸运的是,Python可以很好的记住运行到哪一行,函数每次调用完成,程序继续回到刚刚没有完成的并被调用的函数。运行完所有代码,便结束。
说明了什么?当你阅读一个程序的时候,不用每次都从第一行到最后一行。有些时候按照运行流程来阅读会更容易理解。
形参和实参
我们看到一些内建函数需要实参。例如,调用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
参数会在函数调用之前计算,所以在上面的例子中,'Span '*4 和math.cos(math.pi)只计算了一次。
也可以使用变量作为函数的实参:
>>> michael = 'Eric, the half a bee.' >>> print_twice(michael) Eric, the half a bee. Eric, the half a bee.
(略)
变量和形参是局部的
在函数内部定义一个变量,它是局部的,也就是说它只在函数内部有效,例如:
def cat_twice(part1, part2): cat = part1 + part2 print_twice(cat)
这个函数有两个参数,将它们连接起来,并打印两次,这里有一个运行的例子:
>>> line1 = 'Bing tiddle ' >>> line2 = 'tiddle bang.' >>> cat_twice(line1, line2) Bing tiddle tiddle bang. Bing tiddle tiddle bang.
当cat_twice运行结束,变量cat被回收。如果我们尝试打印它,会报错:
>>> print cat NameError: name 'cat' is not defined
形参也是局部变量。例如,函数print_twice外部,不存在bruce变量。
栈图
为了明白每个变量在什么地方可用,有时候画一个栈图非常有用。就像程序流程图,栈图指明每个变量的值,也指明变量属于哪个函数。
每个函数用一个框架表示。框架是一个旁边为函数名的方框,方框里面是函数的参数和变量名。上面的例子的栈图如图3-1.
框架被分配在栈内存,用来表明哪个函数调用哪个,等等。在这个例子中,print_twice被cat_twice调用,cat_twice被__main__调用,__main__是顶层框架的特殊名称。如果创建一个变量,在任何函数外部,它属于__main__。
每一个形参指向形参相同的值。因此,part1的值和line1相同,part2的值和line2相同,bruce的值和cat相同。
如果函数调用的过程中发生错误,Python打印函数名称和调用它的函数名称,以及调用改函数的函数名称,如此下去,到__main__为止。
例如,你尝试在print_twice中使用cat,将会产生一个变量名错误:
Traceback (innermost last): File "test.py", line 13, in __main__ cat_twice(line1, line2) File "test.py", line 5, in cat_twice print_twice(cat) File "test.py", line 9, in print_twice print cat NameError: name 'cat' is not defined
这一串函数叫做回溯。它告诉你哪个程序文件的哪一行,执行哪个函数时发生错误。展示发生错误的哪一行代码。
回溯的函数的排序和栈图中框架的排序一样。错误发生时,正在运行的函数列在最下方。
有返回值和没有返回值的函数
一些函数我们用于产生结果,例如数学函数;因为没有更好的名称,我们把它叫做有返回值的函数。另外一些函数,例如print_twice,会运算,但是没有返回值。它们叫没有返回值的函数。
当你调用一个有返回值的函数时,大多是想使用函数结果;例如,将它复制给一个变量或者用它作为表达式的一部分:
x = math.cos(radians)
golden = (math.sqrt(5) + 1) / 2
如果在交互环境调用函数,Python会展示函数结果:
>>> math.sqrt(5)
2.2360679774997898
但是,在脚本中,如果仅仅调用一个有返回值的函数,返回值会丢掉!
math.sqrt(5)
这个脚本计算了5的平方根,但是没有保存或者展示结果,因此它并没有用。
没有返回值的函可能会在屏幕上输出,或者有一些其它效果,但是没有返回值。如果你尝试将结果复制给某个变量,会得到一个特殊的值,叫做None。
>>> result = print_twice('Bing') Bing Bing >>> print result None
None值和字符串'None'不同。它是一个特殊的值,有自己的类型:
>>> print type(None) <type 'NoneType'>
目前我们写的所有函数都是没有参数的。在后面一些章节中,会开始写有参数的函数。
为什么使用函数?
或许还不清楚,为什么值得,麻烦把程序划分成函数。有几个原因:
- 创建函数可以将一段代码命名,这样利于程序阅读和调试。
- 让程序看起来更简洁,因为去掉重复代码。另外,如果需要改动它,只需要改动一次。
- 将很长的程序划分成函数,可以让你只调试某一段代码,调试好后再将它们放在一起运行。
- 设计良好的函数可以应用于多个程序。写好并调试好一个函数,可多次使用。
使用from导入
Python提供两种方式导入模块;我们已经见过一种:
>>> import math >>> print math <module 'math' (built-in)> >>> print math.pi 3.14159265359
如果导入math,会得到一个名叫math的模块对象。模块对象包含常数,例如pi,和函数,例如,sin和exp。
但是如果你想直接使用pi,会得到一个报错。
>>> print pi Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'pi' is not defined
另一种方法,你可以从模块中导入对象,例如:
>>> from math import pi
现在可以直接使用pi了,不需要点标记。
>>> print pi 3.14159265359
或者,可以使用星号(*)导出模块的所有内容:
>>> from math import * >>> cos(pi) -1.0
导入math模块所有内容的好处是代码会变得更简洁。坏处是不同模块的命名可能会发生冲突,或者模块与工作环境中定义的变量名冲突。
调试
如果使用文本编辑器写脚本,可能运行时会由于使用了空格和制表符出错。避免的最好方法是只使用空格(不使用制表符)。很多文本编辑器知道Python,默认会这样做,但是一些不会。
制表符和空格都是看不见的,因此很难调试,所以,为自己找一个便于缩进的编辑器。
在运行之前,不要忘记保存程序。一些开发环境会自动保存,但一些不会。这样,有可能编辑器里面你正在看着的程序和实际运行的不一样。
调试可能会花很长时间,如果你一直运行这个一样的,不正确的程序。
保证你看到的代码就是运行的。如果不确定,输入一些类似 print 'Hello' 的代码在程序最前面,再次运行。如果没有看见hello,说明没有正确运行代码。
词汇表
函数:
一段被命名的语句,用来执行某个操作。函数可能有参数和没有参数,也可能有返回值和没有返回值。
函数定义:
创建一个新函数的语句,指明函数的名称,参数,和要执行的代码。
函数对象:
函数定义创建的一个值。函数名是一个指向函数对象的变量。
(函数)头:
函数定义的第一行
(函数)体:
函数定义中的一段语句。
形参:
函数中使用的一个变量名,指向实参传递过来的值。
函数调用:
执行函数的代码。由函数名称和跟在后面的实参列表组成。
实参:
函数执行时传递给其的一个值。这个值传递给函数定义时相应的形参。
局部变量:
函数内部定义的变量。局部变量仅能在函数内部使用。
返回值:
函数的结果。如果函数作为一个表达式调用,那么返回值也是表达式的值。
有返回值的函数:
一个有返回值的函数。
没有返回值的函数:
不返回任何值的函数。
模块:
一个文件,它包含了相关的函数和一些其他定义。
导入语句:
一个语句,可以读取模块文件和创建模块对象。
模块对象:
import语句创建的一个值,可以通过它访问模块中定义的值。
圆点记法:
一种调用模块中函数的语法,写出模块的名称,跟一个点(实心句号),然后再写函数的名称。
composition:
用表达式作为另外一个更大的表达式的一部分,或者语句作为另外一个更大语句的一部分。
运行流程:
程序运行的过程中,语句运行的顺序。
栈图:
通过图表示函数的栈,包括变量,和变量指向的值。
框架:
栈图中用来表示一个函数调用的方框。包括局部变量和函数的形参。
回溯:
运行的函数列表,当错误发生时输出。