我要翻译《Think Python》- 005 第三章 函数

本文翻自:Allen B. Downey ——《Think Python》
原文链接:http://www.greenteapress.com/thinkpython/html/thinkpython004.html
翻译:Simba Gu

[自述:感谢coneypo网友的建议,译文的排版和书写规范已经稍作调整,希望看起来能比前面翻译的几篇舒服一些 :)]

 

第三章 函数


3.1 函数调用

  在程序中,函数可以理解为一个有名字的执行命令的序列。当你在定义一个函数的时候,必须为其指定一个名字,然后再通过这个名字来调用。正如前一章里面提到过的函数调用的例子:

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

  这里的type就是一个函数,括号里面的表达式叫做参数,它返回传入参数的类型。通常来说就是函数“接收”值并“返回”结果,这个结果叫做返回值。

 

3.2 类型转换函数

  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'

  

3.3 数学函数

  Python提供了一个包含大多数常用的数学函数的模块,这个模块是一个包含数学相关函数的集合的文件,在使用里面函数之前,需要用import命令导入一下:

>>> 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函数以分贝为单位来计算信噪比(假设变量 signal_power 和 noise_power已经定义),math模块也提供了以log函数计算以e为底的对数。

  第二个例子是求radians的正弦值,从参数的名字我们还可以联想到除了例子里面出现的 sin 函数以外,还有其它的相关的三角函数,如 cos,tan 等等,这些函数都有一个弧度参数。

  角度转换为弧度的公式为:角度 / 360 * 2 π:

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

  上面表达式中的 math.pi 表示从math 模块中获取pi的值,它的值等于圆周率π的近似值,大概精确到15位小数。如果你想更多的了解三角函数,你可以拿前面例子的计算结果跟平方根除以2来验证一下。

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

  

3.4 组合

  到目前为止,我们已经初步了解了程序的基本元素:变量、表达式和语句,记下来我们将讨论如何把它们组合起来。编程语言最实用的特性就是构建功能块并组合起来实现特定功能。函数的参数可以是任何表达式,甚至可以是算术运算符,例如:

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

 

3.5 建立新函数

  目前,我们只学习了如何使用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个空格(或一个tab缩进符),除此以外,函数体可以包含任意数量的语句。
  print语句中的字符串可以用一对单引号或者一对双引号括起来,大部分人习惯用单引号,但是如果引用的字符串包含单引号(撇号),那就建议使用双引号,反之亦然。如果你在交互模式下面定义函数,解释器会打印省略号(...)来提醒你函数定义尚未结束:

>>> 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_lyrics 是一个函数类型的函数对象,调用自定义函数的语法与调用内置函数的方法是一样的: 

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

  当你定义好了一个函数,你也可以在其他函数里面调用它。如果我们要重复打印print_lyrics函数的内容,我们可以写一个新函数 repeat_lyrics 来实现这个功能:

def repeat_lyrics():
print_lyrics()
print_lyrics()

  当调用函数 repeat_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.

     当然,这首歌实际上不是这么唱的。

 

3.6 定义和引用

  将前面一节里面的代码放在一起如下所示:

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。函数定义的语句也会被执行,但仅限于建立函数对象并不产生输出内容,只有当函数被调用的时候,函数体里面定义的语句才会被执行并输出数据。

  因此正如你所想的那样,在调用函数之前必须先进行定义,也就是说,在你调用函数之前,函数定义部分必须先被执行。


练习 1

  将程序的最后一行放到第一行,然后运行程序看看会得到什么出错信息。

 

练习 2

  将函数调用的语句放回程序最后,然后把 print_lyrics 函数的定义放到 repeat_lyrics 函数的后面,然后运行程序看看会得到什么错误信息。

 

3.7 执行流程

  为了确保程序调用之前被定义,我们必须清楚语句执行的顺序,这个就叫做执行流程。程序执行总是从第一行语句开始依次执行,并且一次执行一行。函数的定义是不会改变程序的执行流程的,请牢记函数里面的语句只有在函数被调用的时候才会被执行。函数调用就像执行流程里面的一条弯路,它会跳转并执行函数里定义的语句,结束之后再回到函数调用的地方接着执行下面的语句。
  你只要记住在一个函数里面是可以调用其它函数的,这样会更容易理解。当一个函数里面调用另一个函数的时候,程序必须执行另一个函数里面的语句,并且在完成之后再继续执行这个函数的其它语句。
  幸运的是Python很擅长跟踪,程序在调用函数的时候都能准确的知道调用的位置。当程序终止的时候,它会跳转到程序的最后。
  这样做有什么寓意吗?事实上你并非每次都是从头开始阅读程序代码,只是有时候从头开始阅读代码并遵循执行流程会让你觉得有意义。


3.8 形参和实参

  我们发现一些内置函数是有争议的。例如,当你调用math.sin函数时需要传递一个实参,但是像 matn.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。

 

3.9 变量和形参是局部的

  当你在函数里面定义一个变量的时候,它只能在函数内部使用,所谓局部变量,例如:

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.10 堆栈图

  为了跟踪变量在哪里被使用了,因此有时候画一个堆栈图会很有帮助。与状态图类似,堆栈图可以显示每一个变量的值,但是它还可以显示出每一个变量属于哪一个函数。
  每个函数由一个方框来表示,每个方框边上写下函数的名字,里面写下函数的参数和变量。前面例子的堆栈图如图3.1所示。

 

图3.1:堆栈图


  所有的方框按序列在一个堆栈里,并且标注其调用的对应的函数。在这个例子中,print_twice 被 cat_twice 调用,而cat_twice被__main__调用,__main__是最顶层的方框的特殊名称,你在函数之外定义的所有变量都属于__main__。
  每个参数都引用了其对应实参的值,因此,part1和line1的值相同,part2和line2的值相同,bruce的值则与cat相同。
  如果在调用函数的时候出错了,Python将会打印所调用的函数名称,以及调用该函数的函数名称,并最终回到主函数 __main__。
  例如,如果你在 print_twice 函数访问cat变量就会得到一个NameError:

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

  这个列表中的函数称为回溯,它能告诉你哪个程序文件出错,出错的行,以及正在执行哪些函数,并且能告诉你哪一行代码导致了错误。在回溯列表中的函数顺序跟堆栈图中的函数顺序是一样的,当前运行的函数在最下方。

 

3.11 有返回结果函数和无返回结果函数

  有一些我们使用的函数是有返回值的,例如数学函数。由于没有更好的名字,我暂且称之为有返回结果函数,像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'>

  到目前为止,我们写的函数都是无返回值的函数,接下来的几章我们将开始写一些有返回值的函数。

 

3.12 为什么要用函数

  或许你还不明白为什么要把一个程序拆分成函数,这里有几条理由:

    1. 建立新函数让你有机会给一组代码进行命名,从而也让你的程序变得更加容易阅读和调试。
    2. 函数可以避免重复代码从而使你的程序更精炼,如果处理逻辑发生变化你只需要修改一个地方就可以了。
    3. 把一个很长的程序拆分成许多函数,这样做可以让你一次调试其中一个,然后再把这些函数组合成一个整体。
    4. 对很多程序来说,良好的设计是非常有用的,一旦你编写调试好了,你就可以进行重复利用。

 

3.13 用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

  另外还有一种import方式,你可以直接导入模块里面的一个对象:

>>> from math import pi

  这样你就可以直接获取pi值而不需要点操作。

>>> print pi
3.14159265359

  或者你可以使用星号操作符导入模块内的所有对象:

>>> from math import *
>>> cos(pi)
-1.0

  把math模块所有对象导入进来的好处是可以让代码看起来更简洁,但是不好的地方是在不同模块之间,或者导入的模块里面的变量名与你自己模块里面的变量名可能会有冲突。


3.14 调试

  如果你使用文本编辑器写脚本,可能会遇到空格或者制表符的问题。避免这个问题的最好方法就是都使用空格(不要使用制表符)。大部分Python编辑器默认情况下都是如此设置的,只有部分编辑器不是这样。因为制表符和空格是不可见的,因此很难去进行调试,所以尽量找一个合适的能管理代码缩进的编辑器。
  另外,运行程序之前记得先保存代码,因为有些编辑器会自动保存,而有些编辑器则不会。因此你需要确认运行的程序代码是否跟你编辑的代码是不是一样的,调试的时候如果你没有注意到这一点,而是一遍又一遍的运行没有保存的代码,这将会浪费你很多时间。
  所以,请确保你正在运行的代码就是你正在查看的代码,如果你不确定,就在代码里加一些代码,如 print "Hello" 放在代码最前面再运行,如果没有输出 Hello,那就证明了你运行了不正确的代码。

 

3.15 术语

函数:

  • 一系列命名并且完成一些操作的语句。函数可以有/没有参数,也可以有/没有返回值。

函数定义:

  • 一种用语句建立的新函数,并为其指定名称,参数以及执行语句。

函数对象:

  • 由函数定义建立的一个值。函数名称是引用函数对象的一个变量。

函数头:

  • 函数定义的第一行。

函数体:

  • 函数定义里面的一系列语句

形参:

  • 函数内部用来引用传递参数值的名称

函数调用:

  • 执行函数的语句。它由函数名和参数列表组成。

实参:

  • 函数被调用时传递给函数的值。值被赋值给函数里面对应的形参。

局部变量:

  • 函数内部定义的变量。局部变量只能在函数内部使用。

返回值:

  • 函数的返回结果。如果函数调用是使用了表达式,则返回值就是表达式的值。

有返回值函数:

  • 函数有返回结果。

无返回值函数:

  • 函数没有返回结果。

模块:

  • 包含了相关函数和定义的集合的文件。

导入语句:

  • 读取模块并且定义模块对象的语句。

模块对象:

  • 由import语句建立的值,其可以访问对应模块里面的值。

点语法:

  • 通过指定模块名加点(.)和函数名的方式调用其它模块里面的函数。

组合:

  • 使用表达式作为一个大的表达式的一部分,或者用语句作为一个大的语句的一部分。

执行流程:

  • 程序执行时的语句执行顺序。

堆栈图:

  • 表示函数堆栈的变量和值引用的图形。

框架:

  • 表示函数调用堆栈图中的方框。它包含函数的参数和局部变量。

回溯:

  • 发生异常时打印的正在执行的函数的列表。


3.16 练习

练习 3

  Python提供了一个可以获取字符串长度的内置函数 len,例如 len('allen')的值为5.
  请编写一个函数right_justify ,参数名为 s,实现功能为:打印一个长度为70字符串。

>>> right_justify('allen')
                                                                       allen

 

练习 4

  函数对象是可以赋值给一个变量或者作为一个参数传递的。例如:do_twice 是一个函数,其又一个函数对象参数,并且调用该函数两次:

def do_twice(f):
f()
f()

  这里又一个例子:利用 do_twice 调用 print_spam 函数两次。

def print_spam():
print 'spam'

do_twice(print_spam)

把代码放入脚本并测试。

  1. 修改 do_twice 函数,变成两个参数,一个是函数对象,另一个是值,然后调用这个函数对象两次,传递这个值作为参数。
  2. 编写一个更通用的 print_spam 函数,命名为 print_twice,传递一个字符串参数并且打印两次。
  3. 利用 do_twice 的修改版本调用两次 print_twice,传递一个参数 'spam' 。
  4. 定义一个新函数 do_four,接收一个函数对象参数和一个值参数,并调用函数对象4次,传递一个值参数。函数体应该只有2条语句,而不是4条语句。

答案请参考:http://thinkpython.com/code/do_four.py

 

练习 5

这个练习只能使用我们已经学过的语句来实现。
写一个函数画出如下网格:


+ - - - - + - - - - +
|           |           |
|           |           |
|           |           |
|           |           |
+ - - - - + - - - - +
|           |           |
|           |           |
|           |           |
|           |           |
+ - - - - + - - - - +


提示:打印多个字符串可以用逗号隔开: print '+', '-'
如果打印序列以逗号结尾,则表示该行的打印内容尚未结束,接下来的打印内容会在同一行。
print '+',
print '-'
这条语句的输出结果是 '+ -'.
每一个打印语句结束会另起一行,写一个函数画一个4行4列的网格。

 

参考答案:http://thinkpython.com/code/grid.py

 

#英文版权  Allen B. Downey
#翻译中文版权  Simba Gu
#转载请注明出处

 

posted @ 2018-11-04 21:05  鬼画符  阅读(617)  评论(0编辑  收藏  举报