3. 函数
在程序设计中 , 函数是指用于进行某种计算的一系列语句的组合 ,
且为这个组合起一个名称 , 该名称用于标识该函数 .
定义一个函数时 , 需要指定函数的名称并写下一系列程序语句 .
之后 , 就可以使用名称来 '调用' 这个函数 .
3.1 函数调用
>> > type ( 1 )
< class 'int' >
这个函数的名称是type , 括号中的表达式我们称之为函数的参数 ,
这个函数调用的结果是求得参数的类型 .
我们通常说函数 '接收' 参数 , 并 '返回' 结果 , 这个结果也称为返回值 ( return value ) .
Python提供了一些可以将某个值从一种类型转换为另一个类型的函数 .
int ( ) 函数 : 可以将 '纯整型的字符串' 与 '浮点型' 转为整型 . 如果转换失败 , 则会报错 .
>> > s = '1'
>> > type ( s)
< class 'str' >
i = int ( s)
>> > type ( i)
< class 'int' >
>> > int ( 1.6 )
1
>> > int ( '1.1' )
Traceback ( most recent call last) :
File "<stdin>" , line 1 , in < module>
ValueError: invalid literal for int ( ) with base 10 : '1.1' .
值错误: int ( ) 的文本无效, 基数为10 : '1.1' .
>> > int ( 'a' )
Traceback ( most recent call last) :
File "<stdin>" , line 1 , in < module>
ValueError: invalid literal for int ( ) with base 10 : 'a' .
值错误: int ( ) 的文本无效, 基数为10 : 'a' .
float ( ) 函数 : 可以将 '纯数字字符串' 和 '整型' 转为浮点型 .
>> > float ( 1 )
1.0
>> > float ( '1' )
1.0
>> > float ( '1.1' )
1.1
>> > float ( 'a' )
Traceback ( most recent call last) :
File "<stdin>" , line 1 , in < module>
ValueError: could not convert string to float : 'a' .
值错误: 无法将字符串转换为浮点数: 'a' .
str ( ) 函数 : 可以将任意类型参数转换为字符串 .
>> > str ( 1 )
'1'
>> > str ( 1.1 )
'1.1'
3.2 数学函数
Python有一个数学设计模块 , 提供了大多数常用的数学函数 .
模块 ( module ) : 是指包含一组相关的函数的文件 .
想要使用模块中的函数 , 需要先使用import语句将它导入运行环境 , 例 :
>> > import math
这个语句会创建一个名为math的模块对象 ( module object ) . 如果显示 ( 打印 ) 这个对象 , 可以看到它的一些信息 :
>> > import math
>> > math
< module 'math' ( built- in ) >
模块对象包含了该模块中定义的函数和变量 , 若要访问其中的一个函数 , 需要同时指定模块名称和函数名称 ,
用一个句点 ( . ) 分隔 , 这个格式称为 '句点表示法' ( dot notation ) .
>> > math. pi
3.141592653589793
>> > math. fmod( 10 , 3 )
1.0
3.3 组合
到现在为止 , 我们已经分别了解了程序的基本元素-变量 , 表达式 , 语句 ,
但还没有接触如何将它们有机地组合起来 .
程序设计语句最有用的特性之一就是可以将各种小的构建块 ( building block ) 组合起来 .
例如 : 函数的参数可以是任何类型的表达式 , 包括算术操作符 :
print ( 1 + 2 + 3 )
基本上 , 在任何可以使用值的地方 , 都可以使用任意表达式 ,
只有一个例外 , 赋值表达式的左边必须是变量名称 , 在左边放置任何其它的表达式都是语法错误 .
( 后面我们还会看到这条规则的例外情况 . )
>> > hours = 6
>> > minutes = hours * 60
>> > minutes
360
>> > 1 * 60 = minutes
File "<stdin>" , line 1
1 * 60 = minutes
^
SyntaxError: cannot assign to operator.
语法错误:无法分配给运算符.
3.4 添加新函数
到此 , 我们都只是在使用Python提供的函数 , 其实我们也可以自己添加新的函数 .
函数定义 : 指定新函数的名称 , 并提供一系列程序语句 , 当函数被调用时 , 这些语句会顺序执行 .
def print_hello ( ) :
print ( 'hello_1' )
print ( 'hello_2' )
print ( 'hello_3' )
print_hello( )
def是关键字 , 表示接下来是一个函数定义 .
这个函数的名字是print_hello , 函数名的书写规则和变量的名称一样使用 : 字母 , 数字 , 下划线组成 .
( 开头第一个字符不能是数字 , '避免与关键字' , '已经定义变量名' 同名 . )
函数名后面的空括号表示它不接收任何参数 .
函数定义的第一行称为函数头 ( header ) , 其它部分称为函数体 ( body ) .
函数头应该以冒号结束 , 函数体则应当整体缩进一级 .
依照惯例 , 缩进总是使用 4 个空格 ( 使用几个都可以 , 推荐 4 个 ) , 函数体的代码语句行数不限 .
本例中print语句例的字符串使用单引号括起来 , 单引号和双引号的作用相同 .
代码中所有的引号 ( 包括单引号 , 双引号 ) 都必须是直引号 , 而不是斜引号 .
大多情况下 , 人们使用单引号 , 只在本例中这样的特殊情况下才使用双引号 .
本例中的字符串本身就存在单引号 , ( 这里的单引号作为缩略符号用 , I ' m是I am的缩写 ) .
( 想要在字符串内打印引号 , 则包裹字符串的引号不能与其相同 , 使用另一种引号 . )
如果在交互模式例输入函数定义 , 则解释器会输出省略号 ( . . . ) 提示用户当前的定义还没有结束 .
想要要结束这个函数定义 , 需要输入一个空行 .
>> > def print_hello ( ) :
. . . print ( 'hello_1' )
. . . print ( 'hello_2' )
. . . print ( 'hello_3' )
. . .
>> >
定义一个函数会创建一个函数对象 , 其类型是 'function' .
>> > print_hello
< function print_hello at 0x000001C5AE5CE040 >
>> > type ( print_hello)
< class 'function' >
调用新创建的函数的方式 , 与调试内置函数时一样的 , 函数名后面加上 '括号' 则调用函数 .
>> > print_hello( )
hello_1
hello_2
hello_3
定义好一个函数之后 , 就可以在其它函数中调用它 ,
另写一个函数 , 将调用print_hello函数的代码写在这个函数内 .
>> > def repeat_print ( ) :
. . . print_hello( )
. . . print_hello( )
. . .
>> > repeat_print( )
hello_1
hello_2
hello_3
hello_1
hello_2
hello_3
3.5 定义和使用
将前面一节的代码的片整合起来 , 整个程序如下 :
>> > def print_hello ( ) :
. . . print ( 'hello_1' )
. . . print ( 'hello_2' )
. . . print ( 'hello_3' )
>> >
>> > def repeat_print ( ) :
. . . print_hello( )
. . . print_hello( )
. . .
>> > repeat_print( )
这个程序包含两个函数定义 : print_hello和repeat_print .
函数定义的执行方式和其它语句一样 , 不同的是 , 执行后会创建函数对象 , 函数体里面的语句并不会立即运行 ,
而是等到函数被调用时才执行 . 函数定义不会产生任何输出 .
必须先创建一个函数 , 才能运行它 , 换言之 , 函数定义必须在函数调用之前先运行 .
将调用函数的语句 , 移动到首行 , 让函数的调用会先于函数定义执行 .
运行程序并查看会有什么样的错误信息 .
repeat_print( )
def print_hello ( ) :
print ( 'hello_1' )
print ( 'hello_2' )
print ( 'hello_3' )
def repeat_print ( ) :
print_hello( )
print_hello( )
运行终端显示 :
Traceback ( most recent call last ) :
File "C:\Users\13600\PycharmProjects\test\test.py" , line 1 , in < module >
print_hello ( )
NameError : name 'repeat_print' is not defined .
名称错误:未定义名称 'repeat_print' .
将函数调用那一行放回到末尾 , 并将函数print_hello的定义移动到函数repeat_print定义之后 ,
这个时候运行程序会发生什么?
def repeat_print ( ) :
print_hello( )
print_hello( )
def print_hello ( ) :
print ( 'hello_1' )
print ( 'hello_2' )
print ( 'hello_3' )
repeat_print( )
运行工具窗口显示 :
hello_1
hello_2
hello_3
hello_1
hello_2
hello_3
定义repeat_print ( ) 时并不会执行里面的代码 , 这个时候print_hello ( ) 函数还没有定义 , 也会不会出错 .
在调用repeat_print ( ) 函数时 , print_hello ( ) 已经被定义 , 执行print_hello ( ) 正常运行 .
3.6 执行流程
为了保证函数的定义优先于其调用执行 , 需要知道程序中语句运行的顺序 , 即 '执行流程' .
执行总是从程序的第一行开始 , 语句按照从上到下的顺序逐一执行 .
函数定义并不会改变程序的执行流程 , 但应注意函数体中的语句并不立即执行 , 而是等到函数被调用时运行 .
函数调用可以看作程序运行流程中的一个迂回路径 .
遇到函数调用时 , 并不会直接继续运行下一条语句 , 而是跳到函数体的第一行 ,
继续运行完函数体的所有语句 , 再跳回到原来离开的地方 , 往下执行 .
函数体中可以调用其它函数 , 当程序流程运行到一个函数之中时 , 可能需要运行其它函数中的语句 .
而后 , 当运行那个函数中的语句时 , 又可能再需要调用运行另一个函数的语句 !
Python对于它运行到哪里有很好的记录 , 所有每个函数执行结束后 , 程序都能跳回到它离开的地方 .
直到执行到整个程序的结尾 , 才会结束程序 .
总之 , 在阅读代码时 , 并不总因该按照代码书写的顺序一行行阅读 ,
有时候 , 按照程序执行的流程来阅读代码 , 理解的效果可能会更好 .
3.7 形参和实参
在函数调用阶段 , 某些函数需要传入 '参数' .
有些需要一个参数 , 有些需要多个参数 , 这些参数被称为 '实参' .
在函数内部 , 实参会赋值给称为形参 ( parameter ) 的变量 .
参数有两种 : 函数定义里的形参 ( parameter ) , 以及调用函数时传入的实参 ( argument ) , 这两种是由区别的 .
def print_message ( message) :
print ( message)
这个函数在调用时会把实参的值赋值到形参message上 , 并打印message的值 .
可以将任何值作为参数传递给形参 .
def print_message ( message) :
print ( message)
print_message( 1 )
print_message( 'hello' )
内置函数的组合规则 , 在用户自定义函数上也同样可用 , 可以使用任何表达式作为实参 .
作为实参的表达式会在函数正式调用前先执行 , 计算好结果后才开始执行函数体代码 .
def print_message ( message) :
print ( message)
print_message( 1 * 2 )
print_message( 'Hello' + ' ' + 'World!' )
num = 1
print_message( num)
作为实际传入到函数的变量的名称 ( num ) 和函数定义里形参的名称 ( message ) 没有关系 .
函数内部只关心形参的值 , 而不用关心它在调用前叫什么名字 ,
在print_message ( ) 函数内部 , 大家都叫它message .
3.8 变量和形参是局部的
在函数体内新建一个变量时 , 这个变量时局部的 ( local ) , 即它只存在于这个函数之内 .
def character_splicing ( part1, part2) :
cat = part1 + part2
print ( cat)
character_splicing( 'Hello' , ' World!' )
当character_splicing函数结束时 , 变量cat会被销毁 , 这时再尝试打印它的话 , 会得到一个异常 :
def character_splicing ( part1, part2) :
cat = part1 + part2
print ( cat)
character_splicing( 'Hello' , ' World!' )
print ( cat)
运行工具窗口显示 :
Hello World !
Traceback ( most recent call last ) :
File "C:\Users\13600\PycharmProjects\test\test.py" , line 11 , in < module >
print ( cat )
NameError : name 'cat' is not defined .
名称错误 : 未定义名称 'cat' .
形参也是局部的 , 在print_message函数外部不存在message这个变量 .
def print_message ( message) :
print ( message)
print_message( 1 )
print ( message)
运行工具窗口显示 :
1
Traceback ( most recent call last ) :
File "C:\Users\13600\PycharmProjects\test\test.py" , line 6 , in < module >
print ( message )
NameError : name 'message' is not defined .
名称错误 : 未定义名称 'message' .
3.9 栈图
要跟踪哪些变量在地方使用 , 有时候画一个栈图 ( stack diagram ) 会很方便 .
和状态图一样 , 栈图可以展示每个变量的值 , 不同的是它会展示每个变量所属的函数 .
每个函数使用的一个帧 , 帧在栈图中就是一个带着函数名称的盒子 , 里面有函数的参数和变量 .
line1 = 'Hello'
line2 = ' World!'
def print_message ( message) :
print ( message)
def character_splicing ( part1, part2) :
cat = part1 + part2
print_message( cat)
character_splicing( line1, line2)
图中各个帧从上到下安排成一个栈 , 能够展示出哪个函数被哪个函数被调用了 .
( 例 : __main__中的值 , 出现在character_splicing , 那么就说明在__main__中调用了character_splicing . )
上例中 , print_message被character_splicing调用 , 而Character_splicing被__main__调用 .
__mina__是用于表示整个栈图的图框的特别名字 , 在所有函数之外新建变量时 , 它就是属于__main__的 .
每个形参都指向其对应实参的值 , 例 : part1与line1的值相同 , cat与message的值相同 .
如果调用函数 ( f1 ) 的过程中发生了错误 , Python会打印出函数 ( f1 ) 的名称 , 调用它的函数 ( f2 ) 的名称 ,
以及调用这个调用者 ( f2 ) 的函数名 ( f3 ) , 以此类推 , 一直到__main__ .
例如 : 在print_message中访问一个不存在的变量 , 则会得到一个NameErroe .
def print_message ( ) :
print ( xx)
def character_splicing ( ) :
print_message( )
character_splicing( )
运行工具窗口显示 :
Traceback ( most recent call last ) :
File "C:\Users\13600\PycharmProjects\test\test.py" , line 13 , in < module >
character_splicing ( )
File "C:\Users\13600\PycharmProjects\test\test.py" , line 9 , in character_splicing
print_message ( )
File "C:\Users\13600\PycharmProjects\test\test.py" , line 4 , in print_message
print ( xx )
NameError : name 'xx' is not defined
上面这个函数列表被称为回溯 ( traceback ) , 它告诉你错误出现在哪个程序文件 ,
哪一行导致的错误 , 以及哪些函数正在运行 .
回溯中函数的顺序和栈图中图框的顺序一致 , 当前正在执行的函数在最底层 .
3.10 有返回值函数和无返回值函数
有返回值函数 ( fruitful function ) : 函数执行之后 , 有返回结果 ( 值 ) .
调用一个有返回值的函数时 , 大部分情况下你是想要对这个结果进行某些操作 .
在交互模式中执行 , Python会直接显示结果 , 而脚本中 , 如果没有使用变量存储或打印出来 , 它就没有实际作用 .
>> > import math
>> > math. fmod( 10 , 3 )
1.0
>> >
import math
remainder = math. fmod( 10 , 3 )
print ( remainder)
没返回值函数 ( void function ) : 函数执行之后 , 没有返回结果 ( 值 ) .
无返回值函数可能在屏幕上显示某些东西 , 或者有其它效果 , 但是它们没有返回值 ,
打印函数的执行结果会得到一个特殊的值None .
def print_hello ( ) :
print ( 'hello' )
print ( print_hello( ) )
特殊值None有自己的属性 : NoneType .
print ( type ( None ) )
到目前为止 , 我们自定义的函数都是无返回值的函数 , 之后会学习如何写有返回值的函数 .
3.11 为什么要有函数
为什么要花功夫将程序拆分成函数?
也许刚开始编程的时候这其中的原因并不明晰 , 下面解释都可以作为参考 .
* 新建一个函数 , 可以让你有机会给一组语句命名 , 这样可以让代码更易读和更易调试 .
* 函数可以通过减少重复代码使程序更短小 , 后面如果需要修改代码 , 也只要修改一个地方即可 .
* 将一长段程序拆分成几个函数后 , 可以对每一个函数单独进行调试 , 再将它们组装起来成为完整的产品 .
* 一个设计良好的函数 , 可以在很多程序中使用 , 书写一次 , 复用无穷 .
3.12 调试
你会掌握的一个最重要的技能就是调试 , 虽然调用可能时有烦恼 ,
但它的确实编程活动中最耗脑力 , 最有挑战 , 最有趣的部分 .
在某种程度上 , 调试和刑侦工作很像 , 你会面对一些线索 , 而且必须推导出事情发生的过程 ,
以及导致现场结果的事件 .
调试也像是一种实验科学 , 一旦猜出错误的可能原因 , 就可以修改程序 , 再运行一次 .
如果猜对了 , 那么程序的运行结果会符合预测 , 这样就离正确的程序更近一步 , 如果猜错了 , 则需要重新思考 .
正如夏洛克?福尔摩斯所说 : '当你排除掉所有的可能性, 那么剩下的, 不管多少不可能, 必定是真相' .
对某些人来说 , 编程和调试是同一件事 , 也就是说 , 编程正是不断调试修改直到程序达到设计目的的过程 .
这种想法的要旨是 , 因该从一个能做某些时的程序开始 , 然后做一点点修改 , 并调试修改 ,
如此迭代 , 以确保总有一个可以运行的程序 .
例如 : Linux是包含了数百万行代码的操作系统 , 但最开始只是Linux Torvalds编写的用来研究Inter 80386
芯片的简单程序 . 据Larry Greenfield所说 : 'Linus最早的一个程序时交替打印AAAAA和BBBB' ,
后来这些程序演化成了Linux ' .
3. 13 术语表
函数 ( function ) : 一个有名称的语句序列 , 可以进行某种有用的操作 .
函数可以接收或不接收参数 , 可以返回或不返回结果 .
函数定义 ( function definition ) : 一个用来创建新函数的语句 , 指定函数的名称 , 参数以及它包含的语句序列 .
函数对象 ( function object ) : 函数定义所创建的值 .
函数名可以用作是变量 , 来引用一个函数对象 , ( 函数名是一个指向函数对象的变量 ) .
函数头 ( header ) : 函数定义的第一行 .
函数体 ( body ) : 函数定义内的语句序列 .
形参 ( parameter ) : 函数内使用的 , 用来引用作为实参传入的值的名称 .
函数调用 ( function call ) : 运行一个函数的语句 . 它由函数名称和括号 , 以及括号中的参数列表组成 .
实参 ( argument ) : 当函数调用时 , 提供给它的值 . 这个值会被赋值给对应的形参 .
局部变量 ( local variable ) : 函数内定义的变量 . 局部变量只能在函数体内使用 .
返回值 ( return value ) : 函数的结果 . 如果函数被当作表达式调用 , 返回值就是表达式的值 .
有返回值函数 ( fruiful function ) : 返回一个值的函数 .
无返回值函数 ( void function ) : 总是返回None的函数 .
None : 由无返回值函数返回的一个特殊值 .
模块 ( module ) : 一个包含相关函数 , 以及其它定义的集合文件 .
import语句 ( import statement ) : 读入一个模块文件 , 并创建一个模块对象的语句 .
模块对象 ( 末dule object ) : 使用import语句时创建的对象 , 提供对模块中定义的值的访问 .
句点表示法 ( dot notation ) : 调用另一个模块中函数的语法 , 使用模块名加上一个句点符号 , 再加上函数名 .
组合 ( composition ) : 使用一个表达式作为更大表达式的一部分 , 或者使用语句作为更大语句的一部分 .
执行流程 ( flow of execution ) : 语句运行的顺序 .
栈图 ( stack diagram ) : 函数栈的图形表达式 , 也展示它们的变量 , 以及这些变量引用的值 .
图框 ( frame ) : 栈图中的一个图框 , 表达一个函数调用 . 它包含了局部变量以及函数的参数 .
回溯 ( traceback ) : 当异常发生时 , 打印出正在执行的函数栈 .
3.14 练习
1. 练习1
编写一个函数right_justify , 接收一个字符串形参s ( 值为 : 'monty' ) , 并打印出足够的前导空白 ,
以达到最后一个字符出现在地 70 列上 ( 70 - 5 = 65 , 需要 65 个空格 ) .
提示 : 可以利用字符串的拼接和重复特性 .
另外 , Python提供了一个内置名称len的函数 , 返回一个字符串的长度 , 所有len ( 'allen' ) 的值是 5.
def right_justify ( s) :
space = 65 * ' '
print_message = space + s
print ( len ( print_message) )
print ( space + s)
right_justify( 'monty' )
运行工具窗口显示 :
70
monty
2. 练习2
函数对象是一个值 , 可以将它赋值给变量 , 或者作为实参传递 .
例如 , do_twice是一个函数 , 接收一个函数对象作为实参 , 并调用它两次 :
def do_twice ( f) :
f( )
f( )
下面一个使用do_twice来调用一个print_spam函数两次的实例 :
def print_spam ( ) :
print ( 'spam' )
do_twice( print_spam)
1. 将这个实例存入脚本中并测试它 .
def do_twice ( f) :
f( )
f( )
def print_spam ( ) :
print ( 'spam' )
do_twice( print_spam)
2. 修改do_twice , 让它接收连个实参 , 一个实函数对象 , 另一个是一个值 ,
它会调用函数对象两次 , 并传入哪个值最为实参 .
def do_twice ( f, spam) :
f( spam)
f( spam)
def print_spam ( spam) :
print ( spam)
do_twice( print_spam, 'spam' )
3. 将函数print_twice的定义复制到你的脚本中 .
def print_twice ( bruce) :
print ( bruce)
print ( bruce)
def do_twice ( f, spam) :
f( spam)
f( spam)
def print_spam ( spam) :
print ( spam)
do_twice( print_spam, 'spam' )
4. 使用修改版的do_teice来调用print_twice两次 , 并传入实参 'spam' .
def print_twice ( bruce) :
print ( bruce)
print ( bruce)
def do_twice ( f, spam) :
f( spam)
f( spam)
print_twice( spam)
print_twice( spam)
def print_spam ( spam) :
print ( spam)
do_twice( print_spam, 'spam' )
5. 定义一个新的函数do_four , 接收一个函数对象 , 与一个值 , 使用这个值作为实参调用函数 4 次 .
这个函数的函数体因该只有两条语句 , 而不是 4 条 .
解答 : https : / / github . com / AllenDowney / ThinkPython2 / tree / master / code / do_four . py
def do_twice ( func, arg) :
func( arg)
func( arg)
def do_four ( func, arg) :
do_twice( func, arg)
do_twice( func, arg)
do_four( print , 'spam' )
终端工具显示 :
spam
spam
spam
spam
3. 练习3
注意 : 这个练习因该只用语句和我们已经学过的其它语言特性实现 .
1. 编写一个函数 , 绘制如下表格 :
+ - - - - + - - - - +
| | |
| | |
| | |
| | |
+ - - - - + - - - - +
| | |
| | |
| | |
| | |
+ - - - - + - - - - +
提示 : 要在用一行打印多个值 , 可以使用逗号分隔不同的值 :
print ( '+' , '-' )
这条语句输出的是 '+' '-' .
不带参数的print语句会结束当前行并开始下一行 .
默认情况下 , print会自动换行 , 如果你想改这一行为 , 在结尾打印一个空格 , 可以这样做 :
print ( '+' , end = ' ' )
print ( '-' )
这两条语句输出的是 '+ -' .
line1 = '+ - - - - + - - - - +'
line2 = '| | |'
def print_once ( part) :
print ( part)
def print_triple ( part) :
print ( part)
print ( part)
print ( part)
def print_table ( part1, part2) :
print_once( part1)
print_triple( part2)
print_once( part1)
print_triple( part2)
print_once( part1)
print_table( line1, line2)
2. 编写一个函数绘制类似的表格 , 但是又 4 行 4 列 .
解答 : https : / / github . com / AllenDowney / ThinkPython2 / blob / master / code / grid . py
+ - - - - + - - - - + - - - - + - - - - +
| | | | |
| | | | |
| | | | |
| | | | |
+ - - - - + - - - - + - - - - + - - - - +
| | | | |
| | | | |
| | | | |
| | | | |
+ - - - - + - - - - + - - - - + - - - - +
| | | | |
| | | | |
| | | | |
| | | | |
+ - - - - + - - - - + - - - - + - - - - +
| | | | |
| | | | |
| | | | |
| | | | |
+ - - - - + - - - - + - - - - + - - - - +
line1 = '+ - - - - + - - - - + - - - - + - - - - + '
line2 = '| | | | | '
def print_once ( part) :
print ( part)
def print_triple ( part) :
print ( part)
print ( part)
print ( part)
def print_table ( part1, part2) :
print_once( part1)
print_triple( part2)
print_once( part1)
print_triple( part2)
print_once( part1)
print_triple( part2)
print_once( part1)
print_triple( part2)
print_once( part1)
print_table( line1, line2)