第5 章 函 数 函数也可称为方法,是具有特定功能的程序代码块。前几章中已经多次使用了 Python自带的标准函数,如int()、type()、range()、print()等。通过使用函数,开发者可 以快速调用某些功能,从而不需要从零开始构建程序,大大提升程序的开发效率。函数是 编程语言学习过程中非常重要的一部分内容。 本章将对函数的定义、调用、函数参数类型、参数传递、变量作用域和递归函数等内容 进行讲解,还将对一些重要的特殊函数,如lambda函数、map函数、filter函数等进行 介绍。 5.1 函数的定义与调用 5.1.1 函数概念 函数,是具有特定功能的代码块,其目的是代码可以重复使用。比如第3章示例3.21 的菱形打印小程序,用户只要输入菱形的行数,程序就可以打印出对应的菱形。 如果项目组成员都需要调用这个程序,那么让每个成员都编写一个打印菱形的程序, 显然是较为浪费人力和物力资源的。最好的方式就是将其编写为一个函数,共享给项目 组成员使用。 【示例5.1】 将示例3.21改为函数形式。 .................................................................................. ........................................ .................................................................................. ........................................ 1 def print_lx(rows): #将菱形打印程序转换为函数形式 2 rows = int(rows) 3 half = rows // 2 #整除,分为上下两部分 4 if rows % 2 == 0: #进行奇偶判断 5 up = half 6 else: 7 up = half +1 8 for i in range(1,up+1): 9 print(' '*(up-i),'* '*(2*i-1)) 10 for i in range(half, 0, -1): #反向遍历 11 print(' '*(up-i), '* '*(2*i-1))#打印下半部分的结果 12 13 rows = input("请输入菱形的行数:") 14 print_lx(rows) 视频讲解 Python程序设计基础(微课视频版) 1 14 程序运行结果: .................................................................................. ...................... .................................................................................. ...................... 请输入菱形的行数: 7 * *** ***** ******* ***** *** * 示例5.1把菱形行数作为函数的参数进行传递,需要打印几次菱形,就调用几次 函数。 图5.1 使用者角度的函数调用流程图 从函数使用者角度看,函数就如图5.1所示的 一个黑盒子,用户传入零个或多个参数,该黑盒子 经过一定的处理后,就会返回零个或多个值。使 用者只要知道传递什么参数,能得到什么结果即 可,而不需要了解函数的内部处理过程。 从函数设计者(实现函数的编程人员)的角度 看,在设计函数时,一般要思考以下几个问题: (1)函数中哪些内容是动态变化的,即哪些内 容应该被定义为函数参数,如程序示例5.1的菱形行数; (2)函数要实现什么功能,函数最终给使用者返回什么结果; (3)函数如何实现这些功能,即函数体。 5.1.2 函数定义及调用 Python定义函数的语法结构如下。 .................................................................................. ............ .................................................................................. ............ def 函数名([形参列表]): """函数说明文档""" 函数体 [return [返回值]] 语法释义如下。 (1)def:用来定义函数的关键词。 (2)函数名:必须是一个合法的标识符,函数名应见名知义,建议由小写字母组成, 多个单词间用下画线隔开。 (3)形参列表:函数可包含零个或多个参数,形参不用指定数据类型,多个参数间用 逗号隔开,调用函数时再对形参传递参数值。 (4)函数说明文档:一般用于说明函数的功能及调用方式,函数说明文档可以用三 双引号或三单引号对括起,可选。 (5)函数体:由一条或多条语句组成的代码块,用于实现函数的功能。 (6)return语句:返回函数结果,可选。函数可以没有返回值,也可以有一个或多个 第5章 函数 1 15 返回值,多个返回值会以元组的形式返回。 注意:函数可以没有参数,但函数名后的圆括号不能少;函数体、函数说明文档及 return语句都要保持缩进。 【示例5.2】 定义一个无返回值的函数。 .................................................................................. .................... .................................................................................. .................... 1 def greet_user(name): 2 """向用户问候的函数""" 3 name = name.title() 4 print(f"Hello, {name}!") #打印欢迎语句 5 6 name = "alice" 7 greet_user(name) #函数调用 8 greet_user("bob") #函数调用 程序运行结果: .................................................................................. ...... .................................................................................. ...... Hello, Alice! Hello, Bob! 【示例5.3】 定义一个有返回值的函数。 .................................................................................. .......................... .................................................................................. .......................... 1 def is_leap_year(year): #判断一个年份是否为闰年 2 """判断year 是否为闰年的函数""" 3 if (year % 4 == 0 and year % 100 != 0) or year % 400 == 0: 4 return True 5 else: 6 return False 7 8 year = int(input("Input year:")) 9 is_or_not = "is" if is_leap_year(year) else "is not" #使用简化if 语句返回结果 10 print(f"{year} {is_or_not} a leap year.") 程序运行结果: .................................................................................. ...... .................................................................................. ...... Input year:2000 2000 is a leap year. 通过上述两个程序示例可以看出,函数调用的方式是:函数名(实参列表)。一般来 讲,实参列表中的参数数量要与函数定义时的形参数量相同,参数类型也要一致,否则将 会抛出TypeError错误。 由于Python是解释性语言,因此函数的调用一定要放在函数定义之后,否则解释器 将找不到函数,会抛出NameError错误。当定义了多个同名函数时,解释器调用的是最 近一次定义的函数。 根据函数是否有返回值,函数调用有以下两种方式。 (1)带有返回值的函数调用,通常将函数的调用结果作为一个值处理。 (2)没有返回值的函数调用,通常将函数调用作为一条语句来处理。 注意:不论return语句出现在函数体的什么位置,一旦得到执行,将直接结束函数的 运行。 视频讲解 Python程序设计基础(微课视频版) 1 16 思考与练习 5.1 判断题:定义函数时,可以没有参数,但必须要有一对圆括号。 5.2 判断题:在Python中,函数的使用包括函数定义和函数调用,并且函数调用一 定要放在函数定义之后。 5.3 什么是函数? 函数的作用是什么? 5.4 Python中用来定义函数的关键字是什么? 函数必须包含return语句吗? 5.5 阅读下面的代码,写出其执行结果。 .................................................................................. .............. .................................................................................. .............. 1 def func(x): 2 return x + 2 3 return x + 4 4 5 print(func(3)) 5.2 参数类型与参数传递 5.2.1 形参和实参 在介绍函数参数传递前,首先要明确形式参数和实际参数两个概念。形式参数通常 简称为形参,是指在函数定义时,写在圆括号内的变量。形参不代表任何具体的值,只是 作为一种占位符参与函数体的业务逻辑。实际参数通常简称为实参,是指在函数调用时, 实际传递给形参的值。 程序示例5.3的第1行代码,函数定义is_leap_year(year)中的year就是形参,没有 具体的值,只是作为一种占位符参与函数体的业务逻辑。而程序示例5.3的第9行代码, 则是对is_leap_year()的函数调用,其中的year就是实参,由用户输入的具体值确定。一 般来讲,形参和实参的名称(变量名)可以相同也可以不同,但建议设置为相同的名称。 在Python程序中,定义函数时不需要指定形参的类型,形参类型由函数调用时传递 的实参类型决定。实参与形参通常数量一致,当形参为可变长度参数时,实参可以有多 个,此时实参和形参的数量可以不相同。 根据函数参数传递的特点和形式,可大致将函数参数分为位置参数、关键字参数、默 认值参数、可变长度参数、序列解包参数等。 5.2.2 位置参数 位置参数是指函数调用时,根据函数定义的形参位置,依次将实参的值赋值给形参。 位置参数传递要求实参和形参的数量必须一致,顺序一一对应。位置参数是最常见、最简 单的函数参数传递方式。 【示例5.4】 函数的位置参数传递。 .................................................................................. ...... .................................................................................. ...... 1 def describe_pet(pet_name, pet_type): 2 """描述宠物信息的函数""" 视频讲解 第5章 函数 1 17 .................................................................................. .................... .................................................................................. .................... 3 print(f"I have a {pet_type}.") 4 print(f"It's {pet_name.title()}.") 5 6 pet_name = "Tom" 7 pet_type = "cat" 8 describe_pet(pet_name, pet_type) #按顺序依次将实参的值传给形参 9 describe_pet("Jerry", "mouse") #按顺序依次将实参的值传给形参 程序运行结果: .................................................................................. ............ .................................................................................. ............ I have a cat. It's Tom. I have a mouse. It's Jerry. 5.2.3 关键字参数 关键字参数是函数调用时,以“形参名=实参值”的形式指定形参的实参值。此时形 参出现的顺序可以和函数定义时不一致。关键字参数灵活、方便,调用者不用关注形参定 义时的顺序和位置。 【示例5.5】 函数的关键字参数传递。 .................................................................................. ........................ .................................................................................. ........................ 1 def describe_pet(pet_name, pet_type): #该函数和示例5.4 一致 2 """描述宠物信息的函数""" 3 print(f"I have a {pet_type}.") 4 print(f"It's {pet_name.title()}.") 5 6 pet_name = "bath" 7 pet_type = "dog" 8 describe_pet(pet_type=pet_type, pet_name=pet_name) # 指定形参对应的实参值 9 describe_pet(pet_type="mouse", pet_name="jerry") #指定形参对应的实参值 程序运行结果: .................................................................................. ............ .................................................................................. ............ I have a dog. It's Bath. I have a mouse. It's Jerry. 5.2.4 默认值参数 默认值参数是函数定义时,在形参列表中,直接为形参指定默认值。函数调用时,对 于有默认值的参数,可传值也可不传值。未传值时,将采用默认值;传值时,将用新的值替 换默认值。默认值参数可方便函数调用,减少参数传递数量。 注意:定义带有默认值参数的函数时,默认值参数要出现在函数形参列表的右端,任 何一个默认值参数右边都不能再出现非默认值参数。 Python程序设计基础(微课视频版) 1 18 【示例5.6】 定义一个带有默认值参数的函数。 .................................................................................. ...................... .................................................................................. ...................... 1 def describe_pet(pet_name, pet_type="dog"): #定义函数,并使用默认值参数 2 """描述宠物信息的函数""" 3 print(f"I have a {pet_type}.") 4 print(f"It's {pet_name.title()}.") 5 6 pet_name = "Bath" 7 describe_pet(pet_name=pet_name) #默认值参数不传值 8 describe_pet(pet_name="Jerry", pet_type="mouse") #默认值参数传值 程序运行结果: .................................................................................. ............ .................................................................................. ............ I have a dog. It's Bath. I have a mouse. It's Jerry. 对于函数的参数默认值,可使用“函数名.__defaults__”查看函数所有参数的默认值, 其返回值为一个元组,其中的元素依次为每个参数的默认值。 【示例5.7】 查看参数默认值。 .................................................................................. ................ .................................................................................. ................ 1 def describe_pet(pet_name="bath", pet_type="dog"): #定义函数,并使用默认值参数 2 """描述宠物信息的函数""" 3 print(f"I have a {pet_type}.") 4 print(f"It's {pet_name.title()}.") 5 >>>describe_pet._ _defaults_ _ #运行代码需在命令行中运行 程序运行结果: .................................................................................. .... .................................................................................. .... ('Bath', 'dog') 5.2.5 可变长度参数 可变长度参数是函数定义时,无法确定具体参数的数量,此时可将函数的形参设为可 变长度参数,然后根据调用者传递的实际参数数量来确定参数的长度。 可变长度参数有两种形式:*形参名和**形参名。 *参数名:表示该参数是一个元组类型,可接受多个实参,并将传递的实参依次存放 到元组中,其主要针对以位置传值的实参。 例如披萨店会根据用户的口味来提供不同的配料,那么定义函数时,便可将配料设为 可变长度参数。 【示例5.8】 定义一个制作披萨的函数。 .................................................................................. ............ .................................................................................. ............ 1 def make_pizza(*toppings): #定义函数,并使用可变长度参数 2 """制作披萨的函数""" 3 print("Your pizza has following toppings:") 4 for topping in toppings: 视频讲解 第5章 函数 1 19 .................................................................................. ............ .................................................................................. ............ 5 print("-->", topping) 6 7 make_pizza("pepper") 8 make_pizza("rushroom", "pepper", "raddish") 程序运行结果: .................................................................................. ................ .................................................................................. ................ Your pizza has following toppings: --> pepper Your pizza has following toppings: --> rushroom --> pepper --> raddish 在有多种类型参数的情况下,Python解释器会先匹配位置实参,再匹配任意数量实 参,所以要将可变长度参数放在函数定义的位置参数右边。 【示例5.9】 定义一个制作披萨的函数,并使用多种类型参数。 .................................................................................. ........................ .................................................................................. ........................ 1 def make_pizza(size, *toppings): #定义函数,并使用可变长度参数 2 """制作披萨的函数""" 3 print(f"Your pizza is {size} inches.") 4 print("It has following toppings:") 5 for topping in toppings: 6 print("-->", topping) 7 8 make_pizza(12, "pepper") #先传12 给size 形参,其余给toppings 9 make_pizza(12, "rushroom", "pepper", "raddish") 程序运行结果: .................................................................................. ...................... .................................................................................. ...................... Your pizza is 12 inches. It has following toppings: --> pepper Your pizza is 8 inches. It has following toppings: --> rushroom --> pepper --> raddish **形参名:表示该形参是一个字典类型,可接受多个实参,主要针对以关键字传值的 实参,并将传递的键值对保存到字典中。 【示例5.10】 定义一个水果描述函数。 .................................................................................. ................ .................................................................................. ................ 1 def fruit_info(name, grade,**other_info): #定义函数,并使用可变长度参数 2 """定义水果描述函数""" 3 profile = {} 4 profile["name"]= name 5 profile["grade"]= grade 6 for key, value in other_info.items(): 视频讲解 Python程序设计基础(微课视频版) 1 20 .................................................................................. ................ .................................................................................. ................ 7 profile[key]= value 8 return profile 9 10 fruit = fruit_info("apple", "A++",#通过关键字参数给other_info 传值 11 location="shandong", color="red")#other_info 为字典 12 print(fruit) 程序运行结果: .................................................................................. .... .................................................................................. .... {'name': 'apple', 'grade': 'A++', 'location': 'shandong', 'color': 'red'} 5.2.6 多种类型参数混用* Python支持定义函数时几种不同形式的参数混用,但初学者要谨慎使用,因为使用 不当将导致代码混乱,且降低可读性,使得程序查错困难。 在多种类型参数混用时,要注意以下几点原则。 (1)实参传值时,既可通过位置传值,也可通过关键字传值,但*可变参数后面的参 数只能通过关键字传值。 (2)函数定义时,不能同时包含多个相同类型的可变参数,即多个*参数或多个**参 数,但可同时包含一个*参数和一个**参数,且*参数要放在**参数前面。 (3)当既有可变参数又有普通参数时,会先给普通参数赋值,最后将多余的值存放在 可变参数中,可变参数可以不进行赋值,此时可变参数为空。 (4)带有默认值的参数后面不能包含没有默认值的参数,但可以包含可变参数。 【示例5.11】 定义一个学生信息函数,并使用多种参数混用。 .................................................................................. ...................................................... .................................................................................. ...................................................... 1 def student_info(name, age, *contacts, gender="男",**others): 2 ""定义学生信息函数""" 3 print("="*10, "学生基本信息", "="*10) 4 print("姓名: ", name) 5 print("年龄: ", age) 6 print("性别: ", gender) 7 print("联系方式: ", end="") 8 if len(contacts) == 0: #判断联系方式是否为空 9 print("无") 10 else: 11 for contact in contacts: #将所有联系方式放在一行打印 12 print(contact, end="\t") 13 print() 14 for key, value in others.items(): 15 print(key, ":", value) 16 print("="*34) #结束分割线 17 18 student_info("张三", 19, "手机-138****2222", "QQ-876***567", 身高=175, 籍贯="江西") 19 student_info("张丽", 18, "QQ-358***121", gender="女",职务="班长",学号="006") 20 student_info("张小东", 18, 专业="软件工程",学号="008") 视频讲解 第5章 函数 1 21 程序运行结果: .................................................................................. .......................................................... .................................................................................. .......................................................... ========== 学生基本信息========== 姓名: 张三 年龄: 19 性别: 男 联系方式: 手机-138****2222 QQ-876***567 身高: 175 籍贯: 江西 ================================== ========== 学生基本信息========== 姓名: 张丽 年龄: 18 性别: 女 联系方式: QQ-358***121 职务: 班长 学号: 006 ========== 学生基本信息========== 姓名: 张小东 年龄: 18 性别: 男 联系方式: 无 专业: 软件工程 学号: 008 ================================== 【示例5.12】 对示例5.11进行修改。 .................................................................................. ...... .................................................................................. ...... 1 def student_info(name, age, gender="男", *contacts,**others): 2 #调换gender 与*contacts 位置,后续代码同示例5.11 一致,不再列出 程序运行结果: .................................................................................. .................................. .................................................................................. .................................. ========== 学生基本信息========== 姓名: 张三 年龄: 19 性别: 手机-138****2222 联系方式: QQ-876***567 身高: 175 籍贯: 江西 ================================== Traceback (most recent call last): File "D:\Python\Basic_Python \chap05\ch05_12.py", line 25, in <module> student_info("张丽", 18, "QQ-358***121", gender="女",职务="班长",学号= "006") TypeError: student_info() got multiple values for argument 'gender' 当gender="男"放在*contacts之前时,Python解释器在解释执行第19行代码时, 会先按照位置参数进行传值,gender首先得到值"QQ-358***121",后又继续赋值gender= "女",因此程序报错。 Python程序设计基础(微课视频版) 1 22 【示例5.13】 继续对示例5.11进行修改。 .................................................................................. ...... .................................................................................. ...... 1 def student_info(name, *contacts, age, gender="男",**others): 2 #调换name 之后的参数顺序,后续代码同示例5.11 一致,不再列出 程序运行结果: .................................................................................. .............. .................................................................................. .............. Traceback (most recent call last): File "D:\Python\Basic_Python \chap05\ch05_13.py", line 24, in <module> student_info("张三", 19, "手机- 138****2222", "QQ- 876***567", 身高= 175, 籍贯="江西") TypeError: student_info() missing 1 required keyword-only argument: 'age' 此时第18 行代码中函数参数传递时,contacts得到的值为(19,"手机-138**** 2222","QQ-876***567")。因此,age没有得到赋值,程序报错。实参传值时,可变参数 后面的参数只能通过关键字传值。 【示例5.14】 继续对示例5.11进行修改。 .................................................................................. ...... .................................................................................. ...... 1 def student_info(name, age, *contacts,**others, gender="男"): #语法错误 2 #将gender 置于**others 后,后续代码同示例5.11 一致,不再列出 程序运行结果: .................................................................................. ............ .................................................................................. ............ File "D:\Python\Basic_Python \chap05\ch05_14.py", line 8 def student_info(name, age, *contacts, **others, gender="男"): ^ SyntaxError: arguments cannot follow var-keyword argument 带有**的可变长度参数必须放在函数定义的最后,因此这里程序报错。 5.2.7 参数传递的序列解包* 参数传递的序列解包:实参为序列对象,传值时将序列中的元素依次取出,然后按照 一定规则赋值给相应变量。其主要有两种形式:*序列对象、**字典对象。 当传递的实参为*序列对象时,将会取出序列中的每个元素,然后按照位置顺序依次 赋值给每一个形参。 【示例5.15】 使用*序列实参。 .................................................................................. .............. .................................................................................. .............. 1 def demo(a, b, c): 2 print(f"a = {a}, b = {b}, c = {c}") 3 4 seq = [1, 2, 3] 5 demo(*seq) #实参为*序列对象 程序运行结果: .................................................................................. .... .................................................................................. .... a = 1, b = 2, c = 3 视频讲解