第 5 章 函数与模块 按照结构化程序设计思想,对于一个规模比较大的程序,先按功能划分成若 干模块,每一个模块可以再划分成更小的模块,直至每一个模块完成一个比较单 一的功能,然后分别编写对应于每一个模块的程序,最后再把这些模块组织成一 个完整的程序。这样一种程序设计模式可使程序结构清晰,易于阅读和理解,易 于修改和维护,也便于多人合作编写程序,从而保证程序的质量和开发效率。 在Python语言中,一个模块的功能可由一个或多个函数实现。一个Python 源程序可由若干函数组成,函数之间通过调用关系形成一个完整的程序。 5.1 函数定义 在Python语言中,函数要先定义后调用。函数定义的语法格式如下: def 函数名(形式参数表): 函数体 return 返回值 示例:定义一个函数,求3个数的平均值。 def average(num1,num2,num3): ave=(num1+num2+num3)/3 return ave 结合该示例,对函数定义解释如下: (1)def是用于定义函数的关键字。 (2)函数名用于标识函数。函数定义后,一般要通过函数调用的方式使用这个函数,函 数调用时要用到函数名。函数名要符合标识符的命名规则。当函数名由多个单词组成时, 本书采用非首单词首字母大写的形式,如averageScore。 (3)形式参数表用于给函数运算提供需要的数据。如果有多个形式参数,形式参数之 间用逗号分开。参数名也要符合标识符的命名规则。函数也可以没有形式参数,此时形式 参数表为空,但一对小括号不能省略。上面示例函数的功能是计算3个数的平均值,形式参 数num1、num2、num3用于提供参与计算的3个数值。 (4)函数定义的第一行以冒号(:)结束。该行称为函数首部。 (5)函数体由实现函数功能的一条或多条语句组成。相对于函数首部,函数体要有 缩进。 (6)如果函数的功能是计算出一个或多个结果值,则需要用return语句把函数的结果 带回调用该函数的调用函数(调用程序)。return后面可以跟一个值,也可以跟由逗号分开 Python 语言程序设计(第2 版) 1 14 的多个值。如果函数的功能只是完成一些动作,没有值需要带回调用程序,可以不写return 语句。 5.2 函数调用 如果只有函数定义,并不能发挥实际的作用。一个函数只有被其他函数(或程序)调用 才能被执行,才能实现其定义的功能。调用其他函数的函数(或程序)称为调用函数(或调用 程序),被其他函数调用的函数称为被调用函数。 函数调用的语法格式如下: 函数名(实际参数表) 函数调用时的实际参数(简称实参)个数应与函数定义时的形式参数(简称形参)个数一 致,把每个实参的值传递给对应的形参。每个实参是一个表达式,但函数的各个形参必须是 变量,接收来自实参的值。实参表达式可以是简单表达式,如一个常量或一个变量等;也可 以是比较复杂的表达式,如由常量和变量组成的表达式。 函数调用可以以一个语句的形式出现,这时函数的执行结果不是得到一个或多个返回 值,而是实现特定的功能,如交换两个变量的取值、对一组数据排序、显示多行字符串等。函 数调用也可以出现在表达式中,作为运算对象出现,这时的函数必须有返回值。实际编程 时,定义和调用有返回值的函数的情况比较多。 调用一个函数时,首先计算实参表中各表达式的值,然后调用函数所在的程序暂停执 行,转去执行被调用函数,被调用函数中各形参的初值就是调用函数中各对应实参的值。被 调用函数执行完函数体语句后,返回调用函数继续执行函数调用所在语句后面的语句。 【例5.1】 通过函数计算3个成绩的平均值。 #P0501.py def average(num1,num2,num3): #定义计算平均值的函数 ave=(num1+num2+num3)//3 return ave #主程序,程序执行时从主程序开始 print("请输入3 个成绩:") score1=int(input("score1=")) score2=int(input("score2=")) score3=int(input("score3=")) ave_score=average(score1,score2,score3) #调用函数计算平均成绩 print("平均成绩=",ave_score) 该程序包括两部分:函数定义和函数调用。程序的执行过程如下: (1)程序的执行从主程序开始,先执行主程序中的如下语句: print("请输入3 个成绩:") score1=int(input("score1=")) score2=int(input("score2=")) score3=int(input("score3=")) (2)执行函数调用所在的语句: 第5 章 函数与模块 1 15 ave_score=average(score1,score2,score3) (3)暂停主程序的执行,转去执行被调用函数average()中的函数体: ave=(num1+num2+num3)//3 return ave 其中,3个形参num1、num2、num3的初值分别来自调用函数中实参score1、score2、 score3的值。 (4)当执行完returnave语句后,返回主程序,继续执行如下语句: ave_score=average(score1,score2,score3) print("平均成绩=",ave_score) 其中,被调用函数average(score1,score2,score3)的值就是通过return语句返回的ave 的值。 【例5.2】 定义函数将3个数按由小到大的顺序排序。 #P0502.py def reorder(num1,num2,num3): #定义函数 if num1>num2: num1,num2=num2,num1 #交换变量num1 和num2 中的数据 if num1>num3: num1,num3=num3,num1 #交换变量num1 和num3 中的数据 if num2>num3: num2,num3=num3,num2 #交换变量num2 和num3 中的数据 return num1,num2,num3 #返回排序后的3 个值 #主程序 print("请输入3 个数: ") a=eval(input("a=")) b=eval(input("b=")) c=eval(input("c=")) x,y,z=reorder(a,b,c) #调用函数对3 个数排序 print("排序前3 个数的顺序是:",a,b,c) print("排序后3 个数的顺序是:",x,y,z) 该程序调用的reorder()函数有3个返回值,在x,y,z=reorder(a,b,c)的调用模式中, 把3个返回值直接依次赋给变量x、y、z;也可以用t=reorder(a,b,c)模式调用,由于赋值号 (=)左边只有一个变量名,所以要把3个返回值组织成一个元组赋给变量t,此时的变量t 被看作元组变量。 .................................................................................... 说明: (1)上面的程序代码由两部分组成,前面是函数定义,后面是主程序。程序的执行从主 程序开始,主程序调用函数时,转去执行函数,函数执行结束返回主程序继续执行。 (2)在由主程序和被调用函数组成的程序结构中,数据的输入与输出一般在主程序中 完成,被调用函数通过实参和形参接收来自主程序的数据并进行处理,然后用return语句 将处理结果返回给主程序。有返回值的被调用函数中尽量不要出现输入与输出语句。 .................................................................................... 【例5.3】 定义函数输出指定行数的星号串。 Python 语言程序设计(第2 版) 1 16 #P0503.py def display(n): #定义函数 for i in range(n): print("*************") #函数没有返回值 #主程序 m=int(input("请输入要输出星号串的行数=")) display(m) 由于定义函数display()时没有return语句,即该函数没有返回值,只是完成输出指定 行数的星号串的动作,所以对该函数的调用要以语句形式出现,即display(m)的形式。 5.3 函数的参数传递 调用函数(调用程序)与被调用函数之间的联系是通过参数传递实现的。定义函数时, 系统并不给函数的形参分配内存单元,函数被调用执行时,系统才为各形参分配内存单元, 并把对应的实参的值传递给形参。在Python语言中,实参值传递给形参有两种方式:一是 不改变实参值的传递方式,二是改变实参值的传递方式。 5.3.1 不改变实参值的参数传递 Python语言中的变量(对应的内存单元)并不直接存储某个值,而是存储了值所在内存 单元的地址,这也是在同一个程序中变量类型可以改变的原因,详细介绍见2.5节。在调用 函数时,实参值传递给形参,实际上是将实参对象的地址传递给形参(为便于表述,这里仍简 单地称之为把实参值传递给形参)。如果实参对象是不可变对象(如数值、字符串、元组等), 则有新值就分配新的内存单元,所以执行被调用函数时形参值的改变不会影响实参。 【例5.4】 不改变实参值的参数传递方式。 #P0504.py def fun(x,y): #定义函数 print("x={},y={}".format(x,y)) x=int(x*1.1) y=int(y*1.2) print("x={},y={}".format(x,y)) #主程序 a=20 b=50 print("a={},b={}".format(a,b)) fun(a,b) print("a={},b={}".format(a,b)) 程序执行时的参数传递过程如下。 执行程序时,首先为a和b两个变量赋初值: a=20 b=50 调用执行fun()函数后,把a和b的值分别传递给x、y: x=20 y=50 第5 章 函数与模块 1 17 被调用函数执行完,返回主程序之前,各变量的值如下: x=22 y=60 a=20 b=50 返回主程序后,各变量的值如下(被调用函数中的变量被撤销): a=20 b=50 所以,程序执行结果如下: a=20,b=50 #进入被调用函数前主程序中变量a 和b 的值 x=20,y=50 #刚进入被调用函数时变量x 和y 的值 x=22,y=60 #退出被调用函数前变量x 和y 的值 a=20,b=50 #返回主程序后变量a 和b 的值 .................................................................................... 说明:由于数值是不可变对象,函数中的x=int(x*1.1)语句产生了新值22,所以x指 向新的对象22,同样y指向新的对象60,因此就有了上面的程序执行结果。 .................................................................................... 5.3.2 改变实参值的参数传递 如果实参对象是可变对象(如列表、字典、集合等),运算可在原数据上进行,所以在被调 用函数执行后,形参值的改变会影响到对应的实参值。 【例5.5】 改变实参值的参数传递方式。 #P0505.py def fun(list2,n): #定义函数 print("list2=",list2) for i in range(n): list2[i]=int(list2[i]*1.2) print("list2=",list2) #主程序 list1=[10,20,30,40,50] print("list1=",list1) fun(list1,5) print("list1=",list1) 程序执行结果如下: list1=[10, 20, 30, 40, 50] list2=[10, 20, 30, 40, 50] list2=[12, 24, 36, 48, 60] list1=[12, 24, 36, 48, 60] .................................................................................... 说明:由于列表是可变对象,所以函数中对列表list2的运算可在原值上进行,也就是 在列表list1上进行。 .................................................................................... 5.3.3 位置参数 默认情况下,调用函数时实参的个数、位置要与定义函数时形参的个数、位置一致,即实 Python 语言程序设计(第2 版) 1 18 参是按出现的位置与形参对应的,与参数的名称无关,此时的参数称为位置参数。 【例5.6】 基于位置的参数传递方式。 #P0506.py def fun(x,y): #定义函数 print("x={},y={}".format(x,y)) #主程序 a=x=20 b=y=30 fun(a,b) #实参a、b 的值分别传递给形参x、y fun(x,y) #实参x、y 的值分别传递给形参x、y fun(y,x) #实参y、x 的值分别传递给形参x、y fun(x,x) #实参x、x 的值分别传递给形参x、y 程序执行结果如下: x=20,y=30 #fun(a,b)的结果 x=20,y=30 #fun(x,y)的结果 x=30,y=20 #fun(y,x)的结果 x=20,y=20 #fun(x,x)的结果 从程序执行结果可以看出,实参到形参的对应只是依据参数的位置而定,第一个实参对 应第一个形参,第二个实参对应第二个形参,与参数的名字无关。在示例中,不管实参用的 是与形参不同名的a、b还是与形参同名的x、y或交叉对应的y、x以及两个实参均为x,都 是把第一个实参的值传递给第一个形参,把第二个实参的值传递给第二个形参。 .................................................................................... 说明:实参和形参可以同名,也可以不同名,即使同名也分别对应不同的内存单元。 .................................................................................... 5.3.4 关键字参数 在调用函数时,可以明确指定把某个实参值传递给某个形参,此时的参数称为关键字参 数,关键字参数不再按位置进行对应。 【例5.7】 基于关键字的参数传递方式。 #P0507.py def totalScore(math,lang,puter): #定义函数 total=math+lang+puter average=total//3 return total,average #主程序 print("请输入三门课的成绩: ") math1=int(input("数学=")) lang1=int(input("语文=")) puter1=int(input("计算机=")) total,average=totalScore(math=math1,puter=puter1,lang=lang1) print("总成绩={},平均成绩={}".format(total,average)) 关键字参数的优点是:不需要记住形参的顺序,只需指定哪个实参传递给哪个形参即 可,而且指定顺序也可以和定义函数时的形参顺序不一致,这对于形参个数较多的情形是很 第5 章 函数与模块 1 19 方便的(形参较多时,不容易准确记住形参的顺序),而且能够更好地保证参数传递正确。 5.3.5 默认值参数 一般来说,函数中形参的值是通过实参传递的。如果需要,则可以在定义函数时直接对 形参赋值,此时的参数称为带默认值的参数。在Python语言中,对于带默认值的形参,在 函数调用时,如果没有对应的实参,就使用该默认值;如果有对应的实参,则仍用实参值,覆 盖形参默认值。 【例5.8】 有默认值的参数传递方式。 #P0508.py def area(r=1.0,pi=3.14): #定义函数 return r*r*pi #主程序 print("面积={:.2f}".format(area())) #两个参数都用默认值 print("面积={:.2f}".format(area(2.6))) # 一个参数用默认值 print("面积={:.2f}".format(area(2.6,3.1415926))) #两个参数都不用默认值 程序执行结果如下: 面积=3.14 面积=21.23 面积=21.24 第一次调用area()函数时,由于没有实参,所以程序执行进入area()函数后,两个形参 都取默认值(分别为1.0和3.14);第二次调用时,有一个实参,所以进入area()函数后,形参 r取实参值(2.6),形参pi取默认值(3.14);第三次调用时,由于有两个实参的值,所以进入 area()函数后,两个形参都取实参传过来的值(分别为2.6和3.1415926)。 .................................................................................... 说明: (1)形参的默认值在定义函数时设定。 (2)由于函数调用时实参和形参是按照从左至右的顺序对应的,所以设定默认值的形 参必须出现在形参表的右端,即在有默认值的形参右面不能有无默认值的形参出现。 如下定义函数时的默认值设定是正确的: def fun1(x,y=5,z=10): … 调用函数时,如果只提供一个实参,如fun1(2),将会把实参值2传递给形参x,形参y和z 取默认值。 如下定义函数时的默认值设定是错误的: def fun2(x=3,y,z=10): … 调用函数时,如果只提供一个实参,如fun2(6),将会把实参值6传递给形参x(覆盖x的默 认值),而形参y得不到相应的取值,会引起错误。 .................................................................................... Python 语言程序设计(第2 版) 1 20 5.3.6 可变长度参数 在Python语言中,除了可以定义固定长度参数(参数个数固定)的函数外,还可以定义 可变长度参数的函数。调用此类函数时,可以提供不同个数的参数以满足实际需要,进一步 增强了函数的通用性和灵活性。在定义函数时,可变长度参数主要有两种形式:单星号参 数和双星号参数。单星号参数是在形参名前加一个星号(*),把接收的多个实参值组合在 一个元组内,以形参名为元组名;双星号参数是在形参名前加两个星号(**),把接收的多个 实参值组合在一个字典内,以形参名为字典名。 【例5.9】 单星号可变长度参数。 #P0509.py def totalScore(*score): #定义函数,单星号形参用于把接收的实参值组合为元组 total=0 for i in score: total+=i ave=total//len(score) return total,ave #主程序 total1,ave1=totalScore(78,62,81) #第一次调用,接收3 个实参 print("总成绩={},平均成绩={}".format(total1,ave1)) total2,ave2=totalScore(95,61,72,87) #第二次调用,接收4 个实参 print("总成绩={},平均成绩={}".format(total2,ave2)) 程序执行结果如下: 总成绩=221,平均成绩=73 总成绩=315,平均成绩=78 .................................................................................... 说明: (1)参数score前面有一个星号(*),Python解释器会把形参score看作可变长度参 数,可以接收多个实参,并把接收的多个实参值组合为一个名字为score的元组。 (2)第一次调用totalScore()函数时,将3个数值传递给可变长度形参score,并组合为 包含3个元素、名字为score的元组。 (3)第二次调用totalScore()函数时,将4个数值传递给可变长度形参score,并组合为 包含4个元素、名字为score的元组。 .................................................................................... 【例5.10】 双星号可变长度参数。 #P05010.py def student(**stu): #定义函数,双星号形参用于把接收的实参值组合为字典 num=0 for item in stu.values(): if item=="河北省": num+=1 return num #主程序 count=student(小明="河北省",小亮="北京市",小莲="河北省") print("有{}个学生来自河北省".format(count)) 第5 章 函数与模块 1 21 .................................................................................... 说明: (1)参数stu前面有两个星号(**),Python解释器会把形参stu看作可变长度参数,可 以接收多个实参,并把接收的多个实参值组合为一个名字为stu的字典。 (2)调用student()函数时,将3个实参值传递给可变长度形参stu,并组合为包含3个 元素、名字为stu的字典。 (3)每个实参值应以“键=值”的形式提供,键不需要加引号,值若是字符串则需要加引 号,如示例中的“小明="河北省"”。 .................................................................................... 在定义函数与调用函数时,实参与形参可以如下对应关系出现: (1)实参与形参都是对应的简单类型,如整型、浮点型、字符串等。 (2)实参和形参都是对应的组合类型,如列表、元组、字典、集合等。 (3)实参是简单类型,形参是组合类型,此时需在形参名前加单星号(*)或双星号 (**),前者把接收的实参值组合为元组,后者把接收的实参值组合为字典,此时对实参值有 格式要求。 5.4 函数的嵌套与递归 在Python语言中,函数f1可以调用函数f2,函数f2还可以再调用函数f3,如此下去, 便可形成函数的多级调用。函数的多级调用有两种形式:一是嵌套调用,二是递归调用。 5.4.1 函数嵌套 在函数的多级调用中,如果函数f1、f2…fn各不相同,则称为嵌套调用。 【例5.11】 计算1000~2000的素数之和,用函数的嵌套调用实现。 分析:要实现题目要求的功能,可以设计两个函数。一个是total(),用于求若干个素数 之和;另一个是prime(),用于判定一个数是否为素数。主程序调用total()函数,total()函 数再调用prime()函数,这样使整个程序的结构更为清晰,也降低了每个函数的编写难度。 #P0511.py def prime(n): #定义函数,判断n 是否是素数 for i in range(2,n): if n%i==0: return False #如果不是素数,则返回False else: return True #如果是素数,则返回True def total(n1,n2): #定义函数,计算n1~n2 的素数之和 total=0 for i in range(n1,n2+1): #i 的取值范围为n1~n2 if (prime(i)): #调用函数prime()判断i 是否是素数 total+=i #如果是素数,则进行累加 return total #返回累加和 #主程序 print(total(1000,2000)) #调用函数计算1000~2000 的素数之和 Python 语言程序设计(第2 版) 1 22 程序执行结果如下: 200923 .................................................................................... 说明: (1)定义prime()函数和total()函数时都用到了变量i,这是允许的,两个变量i在各自 的函数内起作用,互不影响。 (2)定义total()函数时,函数名和其中的变量都用到了标识符total,这也是允许的。 .................................................................................... 5.4.2 函数递归 所谓递归,就是将一个较大的问题归约为一个或多个子问题的求解方法。这些子问题 比原问题简单,且在结构上与原问题相同。递归在Python语言中的含义是:在函数的多级 调用中,如果函数f1、f2…fn中有相同的函数,即存在某个函数直接或间接地调用自己,则 称为递归调用。递归调用可以看作嵌套调用的特例。 递归过程包括递推和回归两部分。 例如,求5的阶乘就可以用递归方法,过程如下: 递推过程:5!=5×4!→4!=4×3!→3!=3×2!→2!=2×1! →1!=1。 回归过程:1!=1→2!=2×1=2→3!=3×2=6→4!=4×6=24→5!=5×24=120。 一个问题能够用递归方法解决的关键在于递推有结束点,如阶乘问题的1!=1。如果 递推没有结束点,就无法回归,问题将得不到有效的解决,表现在程序中就是程序的执行永 远也不能结束,这可以称为无效递归。 【例5.12】 用递归方法求n!。 #P0512.py def fact(n): #定义递归函数 if (n==0 or n==1): fac=1 else: fac=n*fact(n-1) # 调 用fact()函数 return fac #主程序 m=int(input("请输入求阶乘的数m=")) print("{}!={}".format(m,fact(m)) 【例5.13】 用递归方法求解汉诺塔问题。 传说在古代印度的贝拿勒斯神庙里安放了一个黄铜座,座上竖有三根宝石柱子。在第 一根柱子上,自上而下按照从小到大的顺序放有64个直径不同的金盘子,形成一座金塔,如 图5.1所示,即所谓的汉诺塔(Hanoi),又称梵天塔。天神让庙里的僧侣们将第一根柱子上 的64个盘子借助第二根柱子全部移到第三根柱子上,即将整个金塔搬迁,同时定下如下3 条规则: (1)每次只能移动一个盘子。 (2)盘子只能在3根柱子上来回移动,不能放在别处。