···························································· 第5 章 chapter5 函 数 5.1 函数入门 在前面的章节中,使用过Python的内置函数和Python标准库中定义的函数,对使 用函数名传入函数参数的函数调用方式已经不再陌生。这些函数实际上是系统已经定 义好的、能够完成特定功能的代码段,只要知道函数的功能、函数名、函数的输入/输出方 式,就可以在程序需要的位置正确地调用函数并利用其功能。但是我们并不知道也不需 要知道函数内部实现的具体代码,所以函数是一种功能的抽象。 用户自定义函数和内置函数的本质是一样的,它们都是函数。本章主要讨论Python 自定义函数。 5.1.1 函数的概念 函数(Function)是一个相对独立的实体,是对完成一定功能的代码段的封装,是一种 功能的抽象。 定义一个函数后,可以在一个程序需要的位置多次调用该函数,调用时给出不同的 参数就可以实现对不同数据的处理,也可以在不同的多个程序中调用,所以函数可以实 现代码复用,这种代码复用可以减少程序代码量。当需要修改功能时,只要在函数中修 改代码,所有调用位置的功能就会被同时更新,所以函数可以降低代码的维护难度。 【例5.1】 分别对3和5、6和8、12和28求最大值。 当不使用函数时,程序代码如下。 1 x, y = 3, 5 2 if x >y: 3 result = x 4 else: 5 result = y 6 print("3 和5 的最大值为{}".format(result)) 78 x, y = 6, 8 9 if x >y: 10 result = x 9 0 ◆P ython 程序设计基础———面向金融数据分析 11 else: 12 result = y 13 print("6 和8 的最大值为{}".format(result)) 14 15 x, y = 12, 28 16 if x >y: 17 result = x 18 else: 19 result = y 20 print("12 和28 的最大值为{}".format(result)) 此程序中,求最大值代码重复执行了3次,而除了初始时x和y的赋值不同,其余代 码均相同。若引入求最大值函数,则代码可以精简为如下。 1 def calMax(x,y): 2 if x >y: 3 return x 4 else: 5 return y 6 7 print("3 和5 的最大值为{}".format(calMax(3,5))) 8 print("6 和8 的最大值为{}".format(calMax(6,8))) 9 print("12 和28 的最大值为{}".format(calMax(12,28))) 可见,引入函数可以使程序更加简洁,有利于缩减代码量,提高重用性。要善于使用 函数,以减少重复编写程序段的工作量。 此外,当编程实现一个较复杂的任务时,为了简化程序设计、便于组织和规划,一般 会将大任务分解成一系列简单的小任务,每个小任务用一段相对独立的功能函数实现。 引入函数后,只需要在主程序中合理地调用各个函数,就可以实现整个程序的功能。所 以函数实现了问题的简化,降低了编程的难度。 例如,2019级金融学一班有50位学生,已知其“程序设计”课程的考试成绩,要求统 计该班的平均分、最高分、最低分、中位分,并按照考试成绩由高到低排序。由于该程序 实现的功能较多、代码较长,因此在编程时可以根据其实现的不同功能将代码划分为不 同的函数,如图5-1所示。 图5-1 函数关系示意 第◆5 章 函数9 1 图5-1中,程序的具体功能由各个不同的函数实现,主程序一般负责调用不同的函 数,用来统筹全局。各个函数既可以被主程序直接调用,也可以被其他函数调用。例如 要计算平均分,可以先调用求和函数得到总分后再计算平均分;要获取最高分、最低分和 中位数,可以先把数据进行排序,这样就可以很好地简化编程。当然,如果主程序要输出 总分和排序后的数据,则可以直接调用求和函数和排序函数。 5.1.2 定义函数 函数定义使用def保留字,包括函数头和函数体两部分,其语法格式如下。 def <函数名>(形参列表): <函数体> 第1行是函数头,以def开头,函数名是合法的Python标识符。一对圆括号中的形 参列表可以包含零个、一个或者多个形式参数。当包含零个形式参数时,圆括号不能省 略。形式参数就是函数头的圆括号中定义的参数,相当于函数的自变量,之所以称为形 式参数,是因为函数定义时形式参数是使用只有名字、不占内存空间的虚拟变量实现的, 等待函数调用时为其传值。 函数体是实现函数功能的语句的集合,是函数定义的主体部分。函数体的最后一条 语句一般是return语句,用于将函数的返回值带回主调函数,并将程序流程从被调函数 转向主调函数。 return语句的语法格式如下。 return [<返回值列表>] 若函数没有返回值,则可以不带<返回值列表>,也可以直接缺省return语句,此时 图5-2 函数定义示例 函数会有一个默认返回值None。Python允许函数 有多个返回值,以逗号分隔。无论函数体有无 return语句,函数执行结束后都会将控制权交还给 调用者。 以求两个数的最大值函数为例,其函数定义如 图5-2所示。 图5-2中,calMax是自定义的函数名,x、y是两 个形参,函数体内的if-else语句用于求x、y两个数 的最大值,最后用return语句返回最大值result。 5.1.3 调用函数 1.函数调用和返回值 定义函数后,其函数体的代码并不会自动执行。只有当函数被调用时,才会执行该 92 ◆P ython 程序设计基础———面向金融数据分析 函数的代码。调用函数的一般形式如下。 函数名( [实参列表] ) 实参即实际参数。当发生函数调用时,实参会给形参传递值。如果调用的是无参函 数,则实参列表可以省略,但圆括号不能省略。 函数调用通常有以下两种方式。 (1)函数调用作为一条语句的一部分 该方式适用于有返回值的函数,调用者利用的是函数的返回值。例如calMax()的函 数调用可写成:y=calMax(5,8),将其函数返回值赋值给变量y。 也可以用“print("Max={}".format(calMax(5,18)))”语句将函数返回值直接输出 到屏幕上。 (2)函数调用单独作为一条语句 这种方式通常适用于无返回值的函数,调用者利用的是函数代码执行的功能流程。 【例5.2】 编写程序,分别为Mary、John和Tom 致欢迎词。 程序代码如下。 1 def printHello(s): 2 print("Hello " + s + ",") 3 print("welcome to China!") 45 printHello("Mary") 6 printHello("John") 7 printHello("Tom") 该程序前3行代码为printHello()的函数定义,无函数返回值(实际函数返回值为 None)。第5~7行是函数调用语句,即调用printHello()函数3次。 执行结果如下。 Hello Mary, welcome to China! Hello John, welcome to China! Hello Tom, welcome to China! 【例5.3】 编程求任意两个数中的最大值并分析其执行流程。 程序的完整代码如下。 1 def calMax(x, y): 2 if x >y: 3 result = x 4 else: 5 result = y 第◆5 章 函数9 3 6 return result 78 a, b = eval(input("请输入两个数:")) 9 m = calMax(a, b) 10 print("Max={}".format(m)) 执行结果如下。 请输入两个数:5,8 Max=8 该程序代码的第1~6行是calMax()的函数定义,第8~10行是主程序代码。当该 程序运行时,由于前6行是函数定义,因此会从第8条语句开始执行。程序的执行流程 如图5-3所示,图中的箭头方向表示程序的执行流向。 图5-3 带函数调用的程序执行流程 执行流程分析如下。 ① 执行第8条语句,即从键盘接收两个数并赋值给变量a和b(假设为5、8); ② 执行第9条语句,由于发生了函数调用,这时主程序会暂停执行,将控制权转移给 calMax()函数。 ③ 函数调用过程如下。 . 形参x、y分别接收实参a、b的值(x,y=5,8)。 . 执行calMax(a,b)的函数体代码,求得最大值result为8。 . 执行return语句,结束函数调用并返回主程序,其返回值为8。 ④ 执行第9条语句,将函数返回值8赋值给变量m。 ⑤ 执行第10条语句,输出“Max=8”,程序执行结束。 2.函数的嵌套调用 Python允许嵌套调用函数,即在调用一个函数的过程中又调用另一个函数。如图5-4 所示,两层嵌套调用的执行流程如下。 ① 执行主程序开始部分。 ② 执行调用f1()函数的语句,流程转向f1()函数。 9 4 ◆P ython 程序设计基础———面向金融数据分析 ③ 执行f1()函数的开始部分。 ④ 执行调用f2()函数的语句,流程转向f2()函数。 ⑤ 执行f2()函数,直到f2()函数结束。 ⑥ 返回到f1()函数中调用f2()函数的位置。 ⑦ 继续执行f1()函数的后续代码,直到f1()函数结束。 ⑧ 返回到主程序中调用f1()函数的位置。 ⑨ 继续执行主程序的后续代码直到结束。 图5-4 嵌套调用函数的执行流程 【例5.4】 从键盘接收一个十进制整数,将其转换为十六进制数并输出。 分析:十进制数转换为十六进制数,可采用除以16取余数法。如十进制数110,其 转换步骤如下。 ① 计算110÷16,商为6,余数为14(即十六进制的E)。 ② 计算6÷16,商为0,余数为6。 一旦商为0,则转换结束。此时,110对应的十六进制数为6E。 程序代码如下。 1 def toHexStr(hexInt): 2 if 0 <=hexInt <=9: 3 return str(hexInt) 4 else: 5 return chr(ord('A') + hexInt - 10) 67 def decToHex(decInt): 8 hexStr = "" 9 while decInt !=0: 10 hexInt = decInt %16 11 hexStr = toHexStr(hexInt) + hexStr 12 decInt = decInt // 16 13 return hexStr 14 15 decInt = eval(input("请输入一个十进制整数:")) 16 hexStr = decToHex(decInt) 17 print("十进制数{}转换为十六进制数为:{}".format(decInt,hexStr)) 第◆5 章 函数95 运行结果如下。 请输入一个十进制整数:245 十进制数245 转换为十六进制数为:F5 该程序采用了嵌套调用函数。程序中定义了两个函数,函数decToHex()的功能是 将十进制数转换为十六进制数。在转换过程中,需要将得到的余数转换为对应的十六进 制字符。由于该功能相对独立,因此将其封装为函数toHexStr(),并在decToHex()函数 中予以调用。当余数为0~9时,只需要用内置函数str()直接将其转换为字符串类型即 可;当余数为10~15时,则需要将其转换为对应的A~F字符。例如若余数为14,则应 转换为字符E。 3.多返回值函数 例5.3和例5.4中的函数只有一个返回值,实际上,函数也可以有多个返回值。下面 通过例5.5介绍多返回值函数。 【例5.5】 求两个整数的最大公约数和最小公倍数。 分析:由于最小公倍数为两个数的乘积除以最大公约数,因此该题的核心是求最大 公约数。求最大公约数的方法有很多,这里采用辗转相除法(又称欧几里得算法),它是 一种典型的迭代算法,具体步骤是:用较大数除以较小数,再用出现的余数(第一余数)除 以除数,再用新出现的余数(第二余数)除以第一余数,如此反复,直到最后余数是0为 止。最后得到的除数就是这两个数的最大公约数。 以15和9为例。 第1步计算15%9,余数为6。 第2步计算9%6,余数为3。 第3步计算6%3,余数为0。 算法结束,最大公约数为3。辗转相除法的执行流程如图5-5所示。 程序代码如下。 1 def calGcdLcm(m, n): 2 a, b = m, n 3 if a <b: 4 a, b = b, a #a 存放大数 5 while b !=0: 6 temp = a %b 7 print("余数为:",temp) 8 a = b 9 b = temp 10 gcd = a #最大公约数 11 lcm = int(m * n / gcd) #最小公倍数 12 return gcd, lcm #多返回值 13 14 x, y = eval(input("请输入两个整数:")) 9 6 ◆P ython 程序设计基础———面向金融数据分析 15 z1, z2 = calGcdLcm(x, y) 16 print("最大公约数为:{}, 最小公倍数为:{}".format(z1,z2)) 图5-5 辗转相除法求最大公约数的流程图 由于calGcdLcm()函数返回了最大公约数和最小公倍数两个值(第12行),因此在 主程序中用z1、z2两个变量分别接收返回值(第15行)。 运行结果如下。 请输入两个整数:9,15 余数为: 6 余数为: 3 余数为: 0 最大公约数为:3, 最小公倍数为:45 第15和16行代码可以换成以下代码,输出效果完全相同。 15 z = calGcdLcm(x,y) 16 print("最大公约数为:{}, 最小公倍数为:{}".format(z[0], z[1])) 多返回值的函数返回的其实是一个元组,元组是一种由圆括号括起来的组合数据类 型,例如上面的第15行代码返回的就是(3,45),赋值给z变量,z[0]和z[1]分别为3和 45这两个元素。关于元组,本书将在第6章详细介绍。 第◆5 章 函数9 7 5.2 函数的参数 在5.1节中,我们初步了解了函数的实参和形参,本节将重点介绍实参和形参的各种 形式,包括位置参数和关键字参数、参数默认值以及可变数量参数。 5.2.1 位置参数和关键字参数 图5-6是函数调用过程中的参数传递与返回值示意图。结合5.1节可以知道,函数 图5-6 函数的参数传递与 返回值示意 定义中给出的是求x、y的最大值,但是此时x、y并无确 定的值,只是形式上有这两个变量而已,因此x、y被称 为形式参数(形参)。 当发生函数调用时,函数形参需要从主调函数那里 获取实际的数据,以明确到底求的是哪两个数的最大 值。此时主调函数给出的实际数据(放在变量a、b中) 被称为实际参数(实参)。实参的值在函数调用时会传 递给形参。 实参有两种类型:位置参数(positionalargument) 和关键字参数(keywordargument)。默认情况下采用 的是位置参数,即按照实参的位置次序依次传值给对应 的形参,图5-6中采用的就是位置参数,实参a、b按照次 序依次传值给形参x、y,此时,实参必须与形参在顺序、个数和类型上相匹配。 当参数较多时,使用位置参数的可读性较差,使用起来很不方便。在这种情况下,可 以采用关键字参数,如函数funStu()的定义如下。 1 >>>def funStu(name, gendor, age, score, city): 2 print("name=",name) 3 print("gendor=",gendor) 4 print("age=", age) 5 print("score=",score) 6 print("city=",city) 若使用位置参数,则其正确的函数调用为 funStu("Mary","Female",18, 98, "Jinan") 但是由于参数较多,一般很难记住它们的顺序。此时就可以使用关键字参数进行函 数调用,方法为 funStu(name="Mary", age=18, gendor="Female", city="Jinan", score=98) 由于在函数调用时指定了相应的关键字(即形参名称),因此实参之间的顺序可以任 9 8 ◆P ython 程序设计基础———面向金融数据分析 意调整,函数的使用会更加容易。 位置参数和关键字参数可以混合使用,但是需要注意:位置参数不能出现在关键字参 数之后。例如,使用“funStu("Mary","Female",18,city="Jinan",score=98)”进行函数调 用是正确的,但是使用“funStu("Mary","Female",age=18,98,"Jinan")”进行函数调用是 错误的,原因就在于位置参数(98,"Jinan")不能在关键字参数(age=18)之后出现。 【例5.6】 求两个二维坐标点之间的欧氏距离,要求实参使用关键字参数。 程序代码如下。 1 from math import sqrt 23 def f_dist(x1,y1,x2,y2): 4 return sqrt((x1 - x2)**2 + (y1 - y2)**2) 56 a,b,c,d = eval(input("请输入坐标值:")) 7 result = f_dist(x1 = a, x2 = b, y1 = c, y2 = d) 8 print("两点间的欧氏距离为:{:.2f}".format(result)) 代码第7行调用f_dist()函数时采用的就是关键字参数。 运行结果如下。 请输入坐标值:3,8,5,9 两点间的欧氏距离为:6.40 5.2.2 参数默认值 定义函数时可以为形参指定默认值。如函数funStu(name,gendor,age,score, city)中的city参数表示学生所在的城市。若将city的默认值设定为Beijing,那么在函数 调用时,如果其对应的实参缺省,则形参会自动取其默认值。 修改funStu()的函数头为 def funStu (name, gendor, age, score, city="Beijing"): 即为city指定了默认值Beijing,其函数调用可以采用如下方式。 funStu ("Liming","Male",19, 87) 运行结果如下。 name=Liming gendor=Male age=19 score=87 city=Beijing