第5章 用函数实现模块化程序设计 前面已经介绍了数值、赋值语句、输入/输出、选择结构、循环结构以及列表等内容,这 些编程方法只适合于解决简单的问题。在解决比较复杂的问题时,经常需要在程序中多 次执行同一项任务。显然,反复编写或复制多份完成同一任务的代码并不是好的方法,这 会使程序变得冗长且难以维护。各种高级语言都提供了代码复用的机制,Python可以使 用函数和类实现代码复用,本章学习函数。 5.1 函数调用 函数是一个“子程序”,也就是程序中的一段代码。函数实现代码复用的基本思想是 给一个语句序列(代码块)取一个名称(函数名),然后在程序的任何位置可以通过引用函 数名称来执行这些语句。也可以说,函数是有名称的代码块。 Python提供了很多内置函数,如前面程序中一直在使用的print()、input()、len() 等,也允许用户自己定义函数。通过函数名执行内置函数或自定义函数的语句序列称为 “调用”函数。 5.1.1 函数调用格式 函数调用的一般格式为 函数名(实参列表) 图5.1 形参、实参与返回值 例如,内置函数pow(x,y)的功能是计算xy,即x的y次方。调用pow()函数如下。 >>> pow(2,5) 32 其中,pow是函数名,x和y是形参(形式参数),2 和5是传递给pow的实参(实际参数)。各个形参或实 参间用逗号分隔。32是函数计算以后得到的结果,称 为返回值,因此说pow(2,5)返回32,如图5.1所示。 第5 章 用函数实现模块化程序设计1 01 如果在表达式中调用函数时Python将函数调用替换为其返回值,例如表达式pow (2,5)+8与32+8等价,则结果为40。 即使函数不接受任何参数,也要在函数名后使用()。()让Python调用指定的函数, 如果不使用(),将输出错误。例如,只输入print时,Python并不执行函数和输出空行,而 是提示print指向一个函数。 >>> print <built-in function print> 有些函数的部分形参会使用默认值。在函数调用时,这些参数既可以传实参,也可以不 传实参而使用默认值。前面章节使用print()函数中的end参数既设置了默认参数值,end 表示结束字符串,默认值为换行符\' n'。在输出时,print()函数将要输出的字符串(或其他类 型对象转换的字符串)与end参数连接在一起,然后输出。例如:print('hello')实际输出的是' hello'+end,也就是输出h' ello\n';print(h' ello',end=\' t')实际输出的是h' ello\t'。 有默认值的形参在传实参时一般要使用参数名,并且应该放在没有默认值的参数后 面,如print(h' ello',end=\' t')。 函数参数表可以包含任意数量的参数,参数数量与函数定义有关,例如 >>> abs(-4) #abs()函数用于计算一个数的绝对值 4> >> max(6,-2) #max()函数和min()函数分别用于求一组数的最大值和最小值 6> >> min(2,-4,6,26.5) -4 5.1.2 不返回值的函数 有些函数无返回值(如print()),Python在执行无返回值的函数时返回一个特殊值 None,表示无返回值。 >>> print('hello') hello >>> x=print('hello') hello >>> print(x) None None既不是字符串,也不是数字,不能用它进行任何有意义的计算。 5.2 定义函数 在Python中,定义一个函数时,需要指定函数的名称和语句序列。 1 02 计算思维与Python 应用编程 5.2.1 函数定义的一般形式 在Python中,函数定义的一般形式为 def 函数名([形式参数1,形式参数2,……]): 语句块 例5.1 定义maxVal()函数求两个数的最大值。 def maxVal(x,y): if x>y: return x else: return y 函数定义以def关键词开头,后接函数名称和圆括号()。括号里面是函数的参数,参 数可以是零个或多个,各个参数间用逗号分隔,冒号后面对应缩进的代码块是函数体。函 数体可以是任何一段Python代码。函数如果需要有返回的计算结果,可利用return关 键字进行返回。return关键字后面可以是数值或其他类型的数据(如字符串、列表、元组 等),也可以是变量或表达式。在执行到return语句时会结束对函数的调用,一个函数可 能会有多个return语句。 函数定义时并不会执行,函数定义中的语句只有在被调用时才会执行。例如下面这 个函数调用。 maxVal(3, 4) 执行时,将3传给x,4传给y。在执行if语句时,条件为假,转else部分,返回4,函数 结束。当 函数被调用时,会执行如下过程。 ① 求实参表达式的值,将求值结果传给函数的形参。例如,调用maxVal(3+4,z)会 在解释器求值后将7传给形参x,将变量z的值传给形参y。 ② 执行函数体的第一条语句。 ③ 执行函数体中的代码,直至遇到return语句。return后面的表达式的值就是函数 调用的值;如果没有语句可以继续执行,函数返回的值为None;如果return后面没有表 达式,函数返回的值也为None。 例5.2 定义函数,根据半径计算圆面积。 def calCircleArea(r): return 3.14159*r*r r=float(input("please input radius:")) area=calCircleArea(r) print("area=",area) 第5 章 用函数实现模块化程序设计1 03 由于函数定义时并不会执行,因此程序执行的第一条语句是输入语句,获得半径r。 然后将r作为参数调用函数calCircleArea(),函数执行完成后,返回值赋值给area变量, 最后输出。 例5.3 定义函数,对列表求和。 def add_list(list): sum=0 for i in list: sum=sum+i return sum array=[1,2,3,4,5,6] s=add_list(array) print("sum=",s) 函数add_list()的参数是列表。在函数体中,利用for语句遍历列表元素求和,最后 将和作为返回值。add_list()函数的功能与sum()相同。 如果实参不是列表,在执行for语句时会产生错误。为避免产生这样的错误,可以在 函数中添加参数类型判断。使用type()函数求列表变量的类型为<classl'ist'>。使用 str()函数将其转换为字符串后,如果字符串中包含l'ist',则为列表,否则直接进行return。 修改后的函数add_list()在参数为非列表类型时,返回None。 def add_list(list): if not 'list' in str(type(list)): #list 不是列表时为真 return #结束函数调用,返回None sum=0 for i in list: sum=sum+i return sum 5.2.2 参数传递方式 Python处理参数的方式要比其他语言更加灵活,有多种参数传递方式。 1.按位置传参数 最常用的参数传递方法是按位置传参数,传入参数的值是按照顺序依次复制的。例 如,函数定义为maxVal(x,y),调用时maxVal(3,4)的第1个实参3传给第1个形参x, 第2个实参4传给第2个形参y。 按位置传参数必须熟记每个位置参数的含义,这在参数个数很多时并不容易。 2.指定参数名称 为避免按位置传参数带来的难以记忆问题,调用函数时可以指定对应形式参数的名 1 04 计算思维与Python 应用编程 称,甚至可以采用与函数定义不同的顺序传递参数。 图5.2 除基取余流程 例5.4 使用函数将十进制数转换成其他进制数。 将十进制转换为其他进制时使用除基取余法,流程如图5.2 所示。第一个余数为最低位。 def convert(n,base): digits=[] while n>0: digits.append(n%base) n=n//base return digits def printConvert(digits): p=len(digits)-1 #逆序输出 while p>=0: print(digits[p],end=" ") p=p-1 printConvert(convert(base=2,n=24)) 3.指定默认参数值 定义函数时,可以为参数指定默认的参数值。调用函数时,如果参数表中没有该参 数,那么该参数使用默认值。 例5.5 使用默认参数值。 Def greet(name,greeting='hello',end="!"): print(greeting,name+end) greet("wangming") greet("wangming","Good morning") greet("wangming",greeting="Hi") 程序运行结果为 hello wangming! Good morning wangming! Hi wangming! 函数可以根据需要使用任意数量的默认参数,但是带默认值的参数不能位于没有默 认值的参数的前面。 4.可变数量参数 函数形参变量名前加*表示不限定参数数量,实参以元组形式传递给形参。 例5.6 按位置传不限数量参数。 def greet(*name,greeting='hello',end="!"): print(name) 第5 章 用函数实现模块化程序设计1 05 names="" for n in name: #将name(元组形式)元素连接成一个字符串 names=names+n+" " print(greeting,names+end) greet("wangming") #name=('wangming',) greet("wangming","liping","Good morning") #name=('wangming', 'liping', 'Good morning') greet("wangming","liping","zhangling",greeting="Hi") #name=('wangming', 'liping', 'zhangling'),greeting="Hi" 程序运行结果为 ('wangming',) hello wangming ! ('wangming', 'liping', 'Good morning') hello wangming liping Good morning ! ('wangming', 'liping', 'zhangling') Hi wangming liping zhangling ! 函数形参变量名前加**表示不限定参数数量,参数名称和参数值以字典形式传递给 形参。参数的名称是字典的键,对应参数的值是字典的值。 例5.7 编写函数,求矩形、圆和梯形面积。 def calArea(type,**args): print(args) #args 是一个字典,包含参数数量不定 if type=="rectangle": #矩形 return args["width"]*args["height"] elif type=="circle": #圆 return 3.14159*args["radius"]*args["radius"] elif type=="trapezoid": #梯形 return (args["top"]+args["bottom"])*args["height"]/2 print("rectangle area=",calArea("rectangle",width=2,height=3)) print("circle area=",calArea("circle",radius=2)) print("trapezoid area=",calArea("trapezoid",top=2,bottom=5,height=3)) 程序运行结果为 {'width': 2, 'height': 3} rectangle area= 6 {'radius': 2} circle area= 12.56636 {'top': 2, 'bottom': 5, 'height': 3} trapezoid area= 10.5 5.2.3 参数类型 从变量指向地址的内容是否可以变化的角度看,数值、字符串、元组是不可变类型,而 1 06 计算思维与Python 应用编程 列表和字典则是可变类型(如图5.3所示)。 图5.3 不可变类型与可变类型 在数值、字符串、元组变量作为函数参数时,如果在函数中修改形参变量的值,不会影 响实参变量本身。 在列表、字典变量作为函数参数时,如果在函数中修改列表或字典内容,函数调用结 束后的实参值也会发生变化。 例5.8 交换函数对不同类型参数的影响。 def swap(x,y): x,y=y,x #修改形式参数x、y,将x 和y 的值互换 def swaplist(list): list[0],list[1]=list[1],list[0] #修改形式参数列表的值,将列表第0 项元素和第 #1 项元素的值互换 a=2 b=3 c=[2,3] swap(a,b) swaplist(c) print("after call swap:","a=",a,"b=",b) print("after call swaplist:","c=",c) 程序运行结果为 after call swap: a= 2 b= 3 after call swaplist: c= [3, 2] 图5.4 交换过程 程序执行过程如图5.4所示。数值对象2和3存储在内存中,变量a是存储2的地址 的名称,b是存储3的地址的名称,或者说a指向2,b指向3。在调用函数swap()时,实 第5 章 用函数实现模块化程序设计1 07 际参数a传递给x,变量x取值为2,也就是存储2的地址又有了一个名称x,或者说x也 指向2。x和y的值交换后,x指向3,y指向2,a和b的值不变。列表c的c[0]位置存储 的是2的地址,访问c[0]时可以通过地址找到2。调用函数swaplist()时,实际参数c传 递给形式参数list,list是列表c的另一个名称,在交换列表list[0]和list[1]时,也是修改 c[0]和c[1]的值。因此,在列表c作为参数时,在函数中修改list就是修改c。函数调用 结束后,c的内容也发生了变化。 例5.9 输入一串字符,编写一个函数,统计字符串中小写字母的个数。 def staLower(inputStr): count=0 for ch in inputStr: if ch>='a' and ch<='z': #小写字符大于或等于'a',并且小于或等于'z' count+=1 return count inputStr=input("please input a string:") print("count=",staLower(inputStr)) 程序运行后,输入一串字符,输出小写字母个数。例如 please input a string:abcdEFG12340xy;* count= 6 5.2.4 lambda()函数 lambda()函数是一种匿名函数,可接受任意数量的参数,但只能有一个表达式。定 义lambda()函数的一般格式为 lambda 参数表: 表达式 其功能是执行表达式并返回结果。 >>> x=lambda a:a+10 >>> print(x(5)) 15 等效于 def x(a): return a+10 >>> p = lambda x,y:x+y >>> print(p(4,6)) 10 5.2.5 pass语句 在Python中,def语句、if语句、for语句和while循环语句必须有一个语句体,即非 1 08 计算思维与Python 应用编程 空缩进代码块。如果遗漏了代码块,则解析程序时会发生语法错误。有些情况下,块中的 代码实际上不需要做任何事情,这时仍然需要在语句体中添加代码。出于这个原因, Python提供了pass语句,它不执行任何操作,但仍然是一个有效的语句。例如 if n%2==0: pass #n 为偶数时,不执行任何操作 else: print(n) #n 为奇数时,输出n 在编写函数时,当代码体还没有实现时,可以用pass语句作为代码体。 5.3 变量的作用域 使用函数涉及的一个重要问题是变量的作用域。变量的作用域是指变量在程序的哪 些地方可以访问。在语句中如果使用在该位置无效的变量,运行时将会产生错误。 例5.10 在程序中使用函数中使用的变量。 def calRectangle(w,h): area=w*h return area print("Rectangle area=",calRectangle(2,3)) print(area) 程序的执行结果如图5.5所示。 图5.5 变量未定义错误 虽然在执行print(area)语句之前在函数中已经使用了area变量,但是在print语句 位置仍然找不到area变量,说明area变量在此处已经失效。 5.3.1 局部变量 在一个函数内部使用的变量是内部变量,它只在本函数范围内有效,也就是说只有在 本函数内才能使用它们,在此函数之外是不能使用这些变量的。这称为局部变量。 图5.6中的area是在函数calRectangle()函数中使用的变量,只在函数中有效,在主 流程中是无效的,因此在执行print(area)语句时找不到该变量。 在程序中添加一条赋值语句area=0,运行时就不会出现错误了。程序中使用的各个 第5 章 用函数实现模块化程序设计1 09 变量的作用域如图5.6所示。 图5.6 局部变量作用域 形式参数也是局部变量,例如形参w和h也只在函数中有效。 不同作用域中可以使用相同名称的变量,它们代表不同的对象,互不干扰。例如,在 主流程作用域中使用的area和在calRectangle()函数中使用的area是同名变量。print (area)语句使用的area的值为0,returnarea语句使用的area的值为6,这两个area是不 同的变量。这表明在设计函数时对变量的命名可以是任意的,不会对其他程序产生影响。 同样,在调用函数时,也不需要考虑函数中使用了哪些变量名。 5.3.2 全局变量 在函数外面声明的变量称为全局变量,程序中的任何函数或代码都可以读取全局变 量。然而,在函数中给全局变量赋值时需要特别小心。 例5.11 在函数中使用程序中声明的变量。 def say_hello(): print('hello',name+'!') def change_name(new_name): name=new_name print("function name:",name) name="wangming" say_hello() change_name("wangmingli") print(name) 程序运行结果为 hello wangming! function name: wangmingli wangming 程序中的name变量是一个全局变量,因为它是在函数外面声明的。在调用say_ hello()函数时,name变量已经存在,say_hello()读取name的值并输出。在调用change_ name()时,将name修改为"wangmingli",然而全局变量name的值并没有改变,在输出 时仍然是"wangming"。实际上Python将change_name()函数中的name变量看成局部