[阅读]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:

  用表达式作为另外一个更大的表达式的一部分,或者语句作为另外一个更大语句的一部分。

运行流程:

  程序运行的过程中,语句运行的顺序。

栈图:

  通过图表示函数的栈,包括变量,和变量指向的值。

框架:

  栈图中用来表示一个函数调用的方框。包括局部变量和函数的形参。

回溯:

  运行的函数列表,当错误发生时输出。

 

posted @ 2015-12-03 17:29  zhaoxianyu  阅读(352)  评论(0编辑  收藏  举报