第十章 函数:定义与参数
10.1 函数的定义
10.1.1 为何需要函数?
任务一:函数的需求分析
函数的需求分析:
代码的组织结构不清晰,可读性差
遇到重复的功能只能重复编写实现相同的代码,冗余度大
对某个功能需要扩展时,需要找出所有实现该功能的程序修改之,无法统一管理,且后期更新难度极大
因此,需要通过某种方式,对需要重复使用的代码进行有组织的打包,便于其他地方使用和统一维护管理。为满足以上需求,在计算机程序中,通过一种称之为函数的编程技术来实现代码复用功能
知识点:
所谓函数,就是把具有独立功能的代码块组织(封装)为一个功能模块中;之后遇到类似的需求,直接使用(调用),而不再需要重复编写
函数包含两个步骤:
定义函数:将代码块封装为功能独立的代码块
调用函数:站在巨人肩膀上,拿来主义,不需要了解函数具体实现细节,享受封装成果
函数的作用:在开发程序时,使用函数可以提高编写的效率以及代码的重用
函数相关的术语:封装、重用、调用
10.1.2 体验函数功能
任务一:体验函数功能
目标:
创建一个python源代码(.py),用于独立管理函数
在主程序中,通过import导入函数,并调用函数
步骤一:在Jupyter中,New->Text File,新建Python源代码(非notebook源代码),File->Rename重命名为multiple99.py,然后将下面的九九乘法表代码复制
1 | # 定义起始行 |
步骤二:根据视频的第3步,将python源代码,定义为一个函数def multiple_table():,然后将上面代码通过缩进方式(选中按Tab键),转化为函数体语句块。保存Ctrl+S并退出
1 | def multiple_table(): |
步骤三:在Jupyter notebook中,添加一个cell,通过import multiple99导入函数模块,并在cell中调用函数,打印输出结果
1 | import multiple |
10.1.3 函数的定义
任务一:函数的定义
函数的定义格式如下:
1 | def fun_name(): |
def 是英文 define 的缩写。python函数定义关键字
fun_name:函数名称。建议使用有意义的命名,能够明确表达函数封装代码的功能,便于后续调用
函数名之后的括号里用于传递函数的参数。可以为空参数,也可以为通过,分割的多个参数
:,指明后续跟着的是函数体(缩进语句块)
函数命名规则类似于变量的命名:
可以由字母、下划线 和 数字组成
不能以数字开头
不能与关键字重名
下面例子为一个简单的函数定义
1 | def greet_user(): |
程序解析:
def greet_user():。使用关键字def来定义函数,函数名为greet_user()
紧跟在def greet_user():后面的所有缩进行构成了函数体
第2行注释被称为文档字符串(docstring),用于描述函数的功能。文档字符串用三引号括起,Python使用它们来生成函数帮助文档
代码行print(“Hello!”)是函数体内的唯一一行代码,greet_user()只做一项工作:打印Hello!
任务二:函数的调用
知识点:
定义好函数之后,只表示这个函数封装了一段代码而已如果不主动调用函数,函数是不会主动执行的
调用函数:需要在程序中,通过 fun_name() 即可完成对函数的调用
例如,Jupyter中,新建一个cell,然后添加下面代码,用于调用之前定义的函数
1 | greet_user() |
任务三:函数演练
需求分析:
编写一个打招呼 say_hello 的函数,封装三行打招呼的代码
在函数下方调用打招呼的代码
1 | name = "小明" |
任务四:函数调用流程分析
任务五:函数调用能否放函数定义之前?
知识点:函数的定义和调用顺序关系
函数调用是不能放函数定义之前
因为在 使用函数名调用函数之前,必须要保证 Python 已经知道函数的存在
否则控制台会提示 NameError: name ‘say_hello’ is not defined (名称错误:say_hello 这个名字没有被定义)
任务六:函数的文档注释
知识点:
函数注释是为了协助开发者(其他人调用函数)能快速掌握函数的调用方式:
函数的功能
函数的参数说明
函数的返回
在开发中,如果希望给函数添加注释,应该在定义函数的下方,使用连续的三对引号。然后在连续的三对引号之间编写对函数的说明文字
在函数调用位置,PyCharm中使用快捷键 CTRL + Q 可以查看函数的说明信息;Jupyter中使用快捷键Shift+Tab
注意:由于函数体相对比较独立,因此在函数定义的上方,应该和其他代码(包括注释)保留两个空行
10.2 函数的参数
10.2.1 向函数传递信息:参数
任务一:需求分析
下面将通过一个两数求和函数,来说明向函数传递参数的必要性
1 | def sum_2_num(): |
思考:
虽然**sum_2_num()**函数实现了两个数的相加,但是该函数功能非常局限
因为两个相加的值num1和num2直接被固定在函数体中
如果能将需要计算的数字,在调用函数时,传递到函数内部就好了
为满足此需求,函数在设计时,可以通过参数向函数传递必要的信息
任务二:向函数传递参数
知识点:函数参数语法
在函数名的后面的小括号内部填写函数的参数
多个参数之间使用 , 分隔
例子1:求和计算函数,传递两个参数,需要计算的数
1 | def sum_2_num(num1, num2): |
例子2:欢迎语打印函数,传递一个参数,用户名
1 | def greet_user(username): |
程序分析:
在函数声明时,**def greet_user()的括号内添加username(形参)**,向函数传递参数
在函数调用时,需要外部程序向函数传递具体的值(实参)给username参数
10.2.2 (重要) 实参与形参
任务一:参数的作用
知识点:
如果把函数比喻成一台机器,那么参数就是原材料,返回值就是最终产品
从一定程度上讲,函数的作用就是根据不同的参数产生不同的返回值
知识点:函数参数,极大的提高了函数的通用性。在函数定义时,对具体的数据使用参数来抽象,那么在设计函数时,只需关注函数本身的业务逻辑和流程处理
在函数定义时,把参数(形参)当做变量使用,重点围绕参数展开函数功能设计和数据计算处理
在函数调用时,向函数传递函数定义时需要的参数,然后通过函数来对这些具体的数据(实参)实现某些功能
任务二:(重要)函数的形参与实参
形参:
英语术语:argument
在函数定义中,出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参
在函数内部作为变量使用
占位符,没有实际数据
实参:
英语术语:parameter
在函数调用时,给出的参数包含了实实在在的数据,这些数据将被传递到函数内部使用,所以称为实际参数,简称实参
是用来把数据传递到函数内部用的
实实在在的数据
1 | def sum_2_num(num1, num2): |
程序分析:
**sum_2_num(num1, num2)**函数实现对两个数的求和计算
形参:在函数定义时,num1和num2在函数体中是个占位符,被称为形参。用于设计函数的逻辑运算:result = num1 + num2
实参:在函数调用时,sum_2_num(50, 20),将50和20数据通过形参num1和num2传递到函数内部。这里的50和20是真实数据,被称为实参
习题:
消息:编写一个名为**display_message()**的函数,它打印一个句子,指出你在本章学的是什么。调用这个函数,确认显示的消息正确无误
1 | def display_message(): |
1 | In the chapter 8, we will learn how to define a function! |
喜欢的图书:编写一个名为favorite_book()的函数,其中包含一个名为title的形参。这个函数打印一条消息,如One of my favorite books is Alice in Wonderland。调用这个函数,并将一本图书的名称作为实参传递给它
1 | def favorite_book(title): |
1 | One of my favorite books is No Longer Human. |
10.2.3 位置实参
知识点:由于函数定义中可能包含多个形参,因此函数调用时也可能包含多个实参。向函数传递实参的方式很多,例如:
位置实参,要求实参的顺序与形参的顺序相同
关键字实参,其中每个实参都由变量名和值组成
将参数使用列表和字典打包传参数
知识迁移:字符串格式化函数**.format()**函数的使用
任务一:位置实参的用法
知识点:
调用函数时,Python必须将每个实参一一对应地与形参进行关联,且不能有任何歧义
最简单的关联方式是基于参数的顺序。因此,这种关联方式被称为位置实参
下面通过显示宠物信息函数来理解位置实参的原理
需求:
输入:输入宠物类型和名字
功能:打印特定的宠物信息
1 | def describe_pet(animal_type, pet_name): |
这个函数的定义表明,它需要两个参数:动物类型animal_type和名字pet_name
调用describe_pet(‘hamster’, ‘harry’)时,需要按顺序提供相应的实参
实参**’hamster’存储在形参animal_type中,实参‘harry’存储在形参pet_name**中
在函数体内,对这两个参数进行相关功能处理,打印输出特定的宠物信息
任务二:多次调用函数
知识点:多次调用函数时,函数的参数和结果是相互独立的
可根据需要调用函数任意次。要再描述一个宠物,只需再次调用**describe_pet()**即可:
1 | def describe_pet(animal_type, pet_name): |
两次调用函数,分别独立地赋予了不同的实参
第二次调用函数时,传递了实参**’dog’和‘willie’**
与第一次调用时类似,通过位置顺序信息,将实参**’dog’关联到形参animal_type,并将实参‘willie’关联到形参pet_name**
任务三:位置实参的顺序很重要
知识点:
使用位置实参来调用函数时,如果实参的顺序不正确,要么程序会崩溃,要么会出现出乎意料的结果
除了某些特殊的函数,本身就可以互换参数,比如求和函数
例如,在调用**describe_pet()**函数时,互换参数,虽然程序也可以输出结果,但是结果变得很荒谬
1 | describe_pet('harry', 'hamster') # I have a harry. # My harry's name is Hamster. |
程序分析:
在这个函数调用中,先指定名字,再指定动物类型
由于实参**’harry’在前,这个值将存储到形参animal_type中;同理,‘hamster’将存储到形参pet_name**中
结果得到了一个名为Hamster的harry
10.2.4 关键字实参
任务一:关键字实参用法
知识点:
关键字实参是传递给函数的名称—值对。(类似字典)
由于在实参中将参数名(key)和值(value)关联起来,所以在传入时无需考虑函数调用中的实参顺序
通过关键字实参还可以清楚地了解函数各参数的意义
例如,print()函数的end参数
关键字实参是传递给函数的名称—值对。下面将通过关键字实参来调用describe_pet(),函数定义与位置实参一样
1 | def describe_pet(animal_type, pet_name): |
函数describe_pet()还是原来那样,但调用这个函数时,明确地指出了各个实参对应的形参
看到这个函数调用时,解释器将实参**’hamster’和‘harry’分别存储在形参animal_type和pet_name**中
关键字实参的顺序无关紧要,因为解释器知道各个值与哪个形参进行关联。
下面两个函数调用是等效的:
1 | describe_pet(animal_type='hamster', pet_name='harry') |
任务二:关键字实参易错点
知识点:两个必须
错误1:使用关键字实参时,必须准确无误地指明函数定义的形参名,否则会出错
错误2:如果位置实参和关键字实参混用时,位置实参必须在关键字实参之前
1 | # 错误的关键字实参 |
1 | # 位置实参必须在关键字实参之前 |
10.2.5 默认值实参
任务一:默认值实参的用法
知识点:
在定义函数时,可给形参指定默认值
在调用函数时,如果为形参指定实参,那么解释器将使用传入的实参;否则,将使用该形参的默认值实参
默认值实参的用法,简化了函数的调用。特别是对于参数非常多的参数,如果使用默认值实参可以极大提升开发效率
例子1:通过Shift+Tab观察**print()函数的定义,该函数共使用了4个默认值实参。如果没有默认值实参,那么高频繁地使用print()**函数将会非常繁琐
例子2:对函数**describe_pet()**使用关键字实参
1 | def describe_pet(pet_name, animal_type='dog'): |
程序解析:
这里修改了函数describe_pet()的定义,在其中给形参animal_type指定了默认值**’dog’**
调用函数时,如果没有给animal_type指定值,Python将把这个形参设置为默认值**’dog’**
知识点:
定义函数时,可以给某个参数指定一个默认值,具有默认值的参数就叫做缺省参数
调用函数时,如果没有传入 缺省参数 的值,则在函数内部使用定义函数时指定的参数默认值
函数的缺省参数,将常见的值设置为参数的缺省值,从而简化函数的调用
例子3:对列表排序程序
1 | gl_num_list = [6, 3, 9] |
任务二:为函数指定参数的缺省值
知识点:
在定义函数时,在形参后使用赋值语句来指定缺省值
缺省参数,需要使用最常见的值作为默认值
如果一个参数的值不能确定,则不应该设置默认值,具体的数值在调用函数时,由外界传递
1 | def print_info(name, gender=True): |
任务三:默认值实参的易错点
易错点:
拥有默认值的形参必须放非默认形参之后
SyntaxError: non-default argument follows default argument。语法错误,非默认参数在默认参数之后
注意:形参的英语是argument
1 | # 将默认实参放非默认实参之前,将会导致错误 |
知识点:
缺省参数的定义位置必须保证带有默认值的缺省参数在参数列表末尾
**def print_info(name, gender=True, title):**是错误的缺省值定义方式
知识点:
调用带有多个缺省参数的函数
在调用函数时,如果有多个缺省参数中部分参数需要传递实参,那么必需要使用关键字实参来指定参数名,这样解释器才能够知道参数的对应关系
1 | def print_info(name, title="", gender=True): |
10.2.6 实参调用的等价形式
任务一:函数调用时的实参等价形式
知识点:
可以混合使用位置实参、关键字实参和默认值实参,通常有多种等效的函数调用方式
注意:如果要使用默认值实参,函数定义的时候必须要有形参赋值为默认值
例子:函数describe_pet()的定义,为形参animal_type提供默认值
1 | def describe_pet(pet_name, animal_type='dog'): |
函数调用说明:
第一个参数:位置实参和关键字实参。在任何情况下都必须给没有默认值的形参pet_name提供实参
第二个参数:位置实参、关键字实参和默认值实参。如果要指定动物类型,可以对animal_type提供实参
下面对这个函数的所有调用都可行:
1 | # 第二个参数为默认值 |
注意:
使用哪种调用方式无关紧要,只要函数调用能生成你希望的输出就行
使用对你来说最容易理解和最简便的调用方式即可
任务二:常见实参调用错误
知识点:实参个数与函数定义的参数不匹配
多于形参个数时,程序肯定会出错
少于形参个数时,程序首先会匹配默认值实参;当不匹配时会出错
例子:如果调用函数**describe_pet()**时没有指定任何实参,结果将如何呢
1 | def describe_pet(pet_name, animal_type='dog'): |
Python发现该函数调用缺少必要的信息,而traceback指出了TypeError: describe_pet() missing 2 required positional arguments。类型错误,该函数缺失2个必须的位置参数
习题:
8-3 T恤:编写一个名为**make_shirt()**的函数,它接受一个尺码以及要印到T恤上的字样。这个函数应打印一个句子,概要地说明T恤的尺码和字样。使用位置实参调用这个函数来制作一件T恤;再使用关键字实参来调用这个函数
1 | def make_shirt(size,letter_sample): |
1 | This T-shirt is in size S, and please print LAKERS on it. |
8-4 大号T恤:修改函数**make_shirt()**,使其在默认情况下制作一件印有字样“I love Python”的大号T恤。调用这个函数来制作如下T恤:一件印有默认字样的大号T恤、一件印有默认字样的中号T恤和一件印有其他字样的T恤(尺码无关紧要)
1 | def make_shirt(size="L",letter_sample="Lakers"): |
1 | Please make a T-shirt is in size L, and print Lakers on it. |
8-5 城市:编写一个名为describe_city()的函数,它接受一座城市的名字以及该城市所属的国家。这个函数应打印一个简单的句子,如Reykjavik is in Iceland。给用于存储国家的形参指定默认值。为三座不同的城市调用这个函数,且其中至少有一座城市不属于默认国家
1 | def describe_city(city,country='china'): |
1 | Shanghai is in China. |
10.3 进阶函数的参数
10.3.1 函数的不可变和可变参数
任务一:变量赋值不影响外部变量
知识点:函数内部赋值变量不影响外部实参变量
形参也是函数的局部变量
变量赋值修改的是变量的引用,即换个贴标签的内存空间
在函数内部,针对参数使用赋值语句,不影响函数外部传递的实参
无论向函数传递的参数是可变还是不可变类型,在函数体内通过赋值修改该局部变量的引用,不影响外部变量的引用
1 | def demo(num, num_list): |
任务二:通过方法修改数据会影响到外部变量
知识点:
如果传递的参数是可变类型,在函数内部,使用方法修改了数据的内容,同样会影响到外部的数据
调用方法并未更改变量的引用,而是直接修改了真实数据的内容
关键点:变量引用(赋值)、实际内存空间(调用修改方法)
1 | def demo(num_list): |
任务三:面试题 +=的使用
知识点:列表变量调用 += 本质上是在执行列表变量的 extend 方法,不会修改变量的引用
1 | def mutable(num_list): |
1 | def demo(num, num_list): |
10.3.2 函数传递不确定数量的参数
任务一:定义支持多值参数的函数
知识点:需求分析
当函数需要接受任意数量的实参,即在函数定义时,并不能确定函数参数数量。为此,Python使用函数的多值参数技术来满足此需求
例如:**print(str1,str2,…)**函数,可以打印数量不定的字符串
知识点:多值参数两种用法
Python函数参数的两种方式:位置实参和关键字实参。那么对应的多值参数方法也有两种:
参数名前增加一个 *** 用于接收元组。对应位置实参**
参数名前增加两个 *** 用于接收字典。对应关键字实参**
一般在给多值参数命名时,习惯使用以下两个名字:
args* :存放元组**参数,前面有一个 *****
*kwargs* :存放字典**参数,前面有两个 *****
args 是 arguments 的缩写,表示参数的含义
kw 是 keyword 的缩写,kwargs 可以记忆键值对参数
1 | def demo(num, *args, **kwargs): |
知识点:
多值参数必须放在非多值(普通)参数之后
Python解释器会先匹配位置实参和关键字实参,然后再将余下的实参都收集到最后个多值参数中
例如,**def demo(*args, num,**kwargs):**将会引起异常
实例:计算任意多个数字的和
需求分析:
定义一个函数 sum_numbers,可以接收的任意多个整数
功能要求:将传递的所有数字累加并且返回累加结果
1 | def demo(*args, **kwargs): |
任务二:元组和字典的拆包
知识点:
在调用带有多值参数的函数时,如果希望:
将一个元组变量,直接传递给 args
将一个字典变量,直接传递给kwargs
就可以使用 拆包,简化参数的传递,拆包的方式是:
在元组变量前,增加一个*
在字典变量前,增加两个*
解包或拆包操作:
先将参数用元组或字典打包,然后在函数传递参数时使用解包操作
使函数参数传递变得很简洁
1 | def demo(*args, **kwargs): |