第6章 函数
6.1函数的创建和调用
提到函数,大家会想到数学函数吧,函数是数学最重要的一个模块,贯穿整个数学学习过程。在Python中,函数的应用非常广泛。在前面我们已经多次接触过函数。例如,用于输出的print() 函数、用于输出的input() 函数及用于生成一系列整数的range() 函数,这些都是Python内置的标志函数,可以直接使用。除了可以直接使用的标准函数外,Python还支持自定义函数。即通过将一段有规律的,重复的代码定义为函数,来达到一次编写、多次调用的目的。使用函数可以提高代码的重复利用率。
6.1.1 创建一个函数
创建函数也称为定义函数,可以理解为创建一个具有某种用途的工具。使用def 关键字实现,具体的语法格式如下:
def functionname([parameterlist]):
['''comments''']
[functionbody]
参数说明:
- functionname:函数名称,在调用函数时使用。
- parameterlist:可选参数,用于指定向函数中传递的参数。如果有多个参数,各参数间使用逗号“,”分隔。如果不指定,则表示该函数没有参数,在调用时也不指定参数。
注意:即使函数没有参数,也必须保留一对空的“()”,否则将显示下图所示错误提示对话框。
![](https://i-blog.csdnimg.cn/blog_migrate/b9c22498c754c78ee7370e5e872b5ff9.png)
语法错误对话框
- '''comments''':可选参数,表示为函数指定注释,注释的内容通常是说明该函数的功能、要传递的参数的作用等,可以为用户提供友好提示和帮助的内容。
说明:……
- functionbody:可选参数,用于指定函数体,即该函数被调用后,要执行的功能代码。如果函数有返回值,可以使用return语句返回。
注意:函数体“functionbody”和注释“'''comments'''”相对于def关键字必须保持一定的缩进。
说明:如果想定义一个什么也不做的空函数,可以使用pass语句作为占位符。
例如,定义一个过滤危险函数filterchar(),代码如下:
def filterchar(string):
'''功能:过滤危险字符(如黑客),并将过滤后的结果输出
about:要过滤的字符串
没有返回值
'''
import re # 导入Python的re模块
pattern = r'(黑客)|(抓包)|(监听)|(Trojan)' # 模式字符串
sub = re.sub(pattern,'@_@',string) # 进行模式替换
print(sub)
运行上面的代码,将不显示任何内容,也不会抛出异常,因为filterchar() 函数还没有被调用。
6.1.2 调用函数
调用函数也就是执行函数。如果把创建的函数理解为创建一个具有某种用途的工具,那么调用函数就相当于使用该工具。调用函数的基本语法格式如下:
functionname([parametersvalue])
参数说明:
- functionname:函数名称,要调用的函数名称必须是已经创建好的。
- parametersvalue:可选参数,用于指定各个参数的值。如果需要传递多个参数值,则各个参数间使用逗号“,” 分隔。如果该函数没有参数,则直接写一对小括号即可。
例如,调用在6.1.1 小节创建的filterchar() 函数,可以使用下面的代码:
def filterchar(string):
'''功能:过滤危险字符(如黑客),并将过滤后的结果输出
about:要过滤的字符串
没有返回值
'''
import re # 导入Python的re模块
pattern = r'(黑客)|(抓包)|(监听)|(Trojan)' # 模式字符串
sub = re.sub(pattern,'@_@',string) # 进行模式替换
print(sub)
about = '我是一名程序员,喜欢看黑客方面的图书,想研究一个Trojan。'
filterchar(about)
调用filterchar() 函数后,将显示如下结果。
我是一名程序员,喜欢看@_@方面的图书,想研究一个@_@。
场景模拟:第4章的实例01实现了每日一贴功能,但是这段代码只能执行一次,如果想要再次输出,还需要再重新写一遍。如果把这段代码定义为一个函数,那么就可以多次显示每日一贴了。
实例01:输出每日一贴(共享版)
def function_tips():
'''功能:每天输出一条励志文字
'''
import datetime # 导入日期时间类
# 定义一个列表
mot = ["今天星期一:\n人生充满了不确定,和惊喜。",
"今天星期二:\n人生亦可燃烧,亦可腐败,我愿燃烧,耗尽所有的光芒。",
"今天星期三:\n毅力和耐性在某种程度上将决定一个人会成为什么样的人。",
"今天星期四:\n微笑拥抱每一天,做像向日葵般温暖的女子。",
"今天星期五:\n志在峰巅的攀登者,不会陶醉在沿途的某个脚印之中。",
"今天星期六:\n别小看任何人,越不起眼的人。往往会做些让人想不到的事。",
"今天星期日:\n我们可以失望,但不能盲目。"]
day = datetime.datetime.now().weekday() # 获取当期星期
print(mot[day]) # 输出每日一贴
#*******************调用函数************************#
function_tips() # 调用函数
运行结果如下:
今天星期六:
别小看任何人,越不起眼的人。往往会做些让人想不到的事。
6.2 参数传递
在调用函数时,大多数情况下,主要函数和被调用函数之间有数据传递关系,这就是有参数的函数形式。函数参数的作用是传递数据给函数使用,函数利用接收的数据进行具体的操作处理。
函数参数在定义函数时放在函数名称的后面的一对小括号中。
6.2.1 了解形式参数和实际参数
在使用函数时,经常会用到形式参数和实际参数,二者都叫作参数,它们的区别将通过形式参数与实际参数的作用来进行讲解,再通过一个比喻和实例进行深入探讨。
1.通过作用理解
形式参数和实际参数在作用上的区别如下:
- 形式参数:在定义函数时,函数名后面括号中的参数为“形式参数”。
- 实际参数:在调用一个函数时,函数名后面括号中的参数为“实际参数”,也就是将函数的调用者提供给函数的参数称为实际参数。
根据实际参数的类型不同,可以分为将实际参数的值传递给形式参数和将实际参数的引用传递给形式参数两种情况。其中,当实际参数为不可变对象时,进行值传递;当实际参数为可变对象时,进行的是引用传递。实际上,值传递和引用传递的基本区别就是,进行值传递后,改变形式参数的值,实际参数的值不变;而进行引用传递后,改变形式参数的值,实际参数的值也一同改变。
例如,定义一个名称为demo 的函数,然后为demo() 函数传递一个字符串类型的变量作为参数(代表值传递),并在函数调用前后分别输出该字符串变量,再为demo() 函数传递以下列表类型的变量作为参数(代表引用传递),并在函数调用前后分别输出该列表。代码如下:
# 定义函数
def demo(obj):
print("原值:",obj)
obj += obj
# 调用函数
print("==========值传递==========")
mot ="唯有在被追赶的时候,你才能真正地奔跑。"
print("函数调用后:",mot)
demo(mot) # 采用不可变对象——字符串
print("函数调用后:",mot)
print("==========引用传递===========")
list1 = ['李白','杜甫','白居易','李清照']
print("函数调用前:",list1)
demo(list1) # 采用可变对象——列表
print("函数调用后:",list1)
上面代码的执行结果如下:
==========值传递==========
函数调用后: 唯有在被追赶的时候,你才能真正地奔跑。
原值: 唯有在被追赶的时候,你才能真正地奔跑。
函数调用后: 唯有在被追赶的时候,你才能真正地奔跑。
==========引用传递===========
函数调用前: ['李白', '杜甫', '白居易', '李清照']
原值: ['李白', '杜甫', '白居易', '李清照']
函数调用后: ['李白', '杜甫', '白居易', '李清照', '李白', '杜甫', '白居易', '李清照']
从上面的执行结果中可以看出,在进行值传递时,改变形式参数的值后,实际参数的值不改变;在进行引用传递时,改变形式参数的值后,实际参数的值也发生改变。
2. 通过一个比喻来理解形式参数和实际参数
函数定义时参数列表中的参数就是形式参数,而函数调用时传递进来的参数就是实际参数。就像剧本选主角一样,剧本的角色相当于形式参数,而演角色的演员就相当于实际参数。
实例02:根据身高、体重计算BMI 指数(共享版)
def fun_bmi(person,height,weight):
'''功能:根据身高和体重计算BMI指数
person:姓名
height:身高,单位:米
weight:体重,单位:千克
'''
print(person + "的身高:" + str(height) + "米\t体重:" + str(weight) + "千克")
bmi=weight/(height*height) # 用于计算BMI指数,公式为:BMI=体重/身高的平方
print(person + "的BMI指数为:"+str(bmi)) # 输出BMI指数
# 判断身材是否合理
if bmi<18.5:
print("您的BMI指数为:"+str(bmi)) # 输出BMI指数
print("体重过轻 ~@_@~")
if bmi>=18.5 and bmi<24.9:
print("您的BMI指数为:"+str(bmi)) #输出BMI指数
print("正常范围,注意保持(-_-)")
if bmi>=24.9 and bmi<29.9:
print("您的BMI指数为:"+str(bmi)) #输出BMI指数
print("体重过重 ~@_@~")
if bmi>=29.9:
print("您的BMI指数为:"+str(bmi)) #输出BMI指数
print("肥胖^@_@^")
# **********************************调用函数************************************* #
fun_bmi("路人甲",1.83,60) # 计算路人甲的BMI指数
fun_bmi("路人乙",1.60,50) # 计算路人乙的BMI指数
运行结果如下。
路人甲的身高:1.83米 体重:60千克
路人甲的BMI指数为:17.916330735465376
您的BMI指数为:17.916330735465376
体重过轻 ~@_@~
路人乙的身高:1.6米 体重:50千克
路人乙的BMI指数为:19.531249999999996
您的BMI指数为:19.531249999999996
正常范围,注意保持(-_-)
>>>
从该实例代码和运行结果可以看出:
(1)定义一个根据身高、体重计算BMI指数的函数fun_bmi(),在定义函数时指定的变量person、height和weight称为形式参数。
(2)在函数fun_bmi()中根据形式参数的值计算BMI指数,并输出相应的信息。
(3)在调用fun_bmi()函数时,指定的“路人甲”、1.83和60等都是实际参数,在函数执行时,这些值将被传递给对应的形式参数。
6.2.2 位置参数
位置参数也称必备参数,是必须按照正确的顺序传到函数中,即调用时的数量和位置必须和定义时是一样的。
1. 数量必须与定义时一致
在调用函数时,指定的实际参数的数量必须与形式参数的数量一致,否则将抛出TypeError异常,提示缺少必要的位置参数。
2. 位置必须与定义时一致
在调用函数时,指定的实际参数的位置必须与形式参数的位置一致,否则将产生以下两种结果。
- 抛出TypeError异常
抛出异常的情况主要是因为实际参数的类型与形式参数的类型不一致,并且在函数中,这两种类型还不能正常转换。
- 产生的结果与预期不符
在调用函数时,如果指定的实际参数与形式参数的位置不一致,但是它们的数据类型一致,那么就不会抛出异常,而是产生结果与预期不符的问题。
说明:由于调用函数时,传递的实际参数的位置与形式参数的位置不一致时,并不会总是抛出异常,所以在调用函数时一定要确定好位置,否则产生Bug,还不容易被发现。
6.2.3 关键字参数
关键字参数是指使用形式参数的名字来确定输入的参数值。通过该方式指定实际参数时,不再需要与形式参数的位置完全一致。只要将参数名字写正确即可。这样可以避免用户需要牢记的参数位置的麻烦,适当函数的调用和参数传递更急灵活方便。
6.2.4 为参数设置默认值
调用函数时,如果没有指定某个参数将抛出异常,为了解决这个问题,我们可以为参数设置默认值,即在定义函数时,直接指定形式参数的默认值。这样,当没有传入参数时,则直接使用定义函数时设置的默认值。定义带有默认值参数的函数的语法格式如下:
def functionname(...,[parameter1 = defaultvalue1]):
[functionbody]]
参数说明:
- functionname:函数名称,在调用函数时使用。
- parameter = defaultvalue1:可选参数,用于指定向函数中传递的参数,并且为该参数设置默认值为defaultvalue1。
- functionbody:可选参数,用于指定函数体,即该函数被调用后,要执行的功能代码。
注意:在定义函数时,指定默认的形式参数必须在所有参数的最后,否则将产生语法错误。
……
6.2.5 可变参数
在Python中,还可以定义可变参数。可变参数也称不定长参数,即传入函数中的实际参数可以是任意多个。
定义可变参数时,主要有两种形式:一种是*parameter,另一种是**parameter。
1. *parameter
这种形式表示接收任意多个实际参数并将其放到一个元组中。例如,定义一个函数,让其可以接收任意多个实际参数,代码如下:
……
2. **parameter
这种形式表示接收任意多个类似关键参数一样显示赋值的实际参数,并将其放到一个字典中。例如,定义一个函数,让其可以接收任意多个显示赋值的实际参数,代码如下:
……
6.3 返回值
到目前为止,我们创建的函数都只是为我们做一些事,做完了就结束。但实际上,有时还需要对事情的结果进行获取。这类似于主管向下级职员下达命令,职员去做,最后需要将结果报告给主管。为函数设置返回值的作用就是将函数的处理结果返回给调用它的程序。
在Python中,可以在函数体内使用return语句为函数指定返回值,该返回值可以是任意类型,并且无论return语句出现在函数的什么位置,只要得到执行,就会直接结束函数的执行。
return语句的语法格式如下:
return [value]
参数说明:
- value:可选参数,用于指定要返回的值,可以返回一个值,也可以返回多个值。
为函数指定返回值后,在掉用函数时,可以把它赋给一个变量(如result),用于保存函数的返回结果。如果返回一个值,那么result中保存的就是返回的一个值,该值可以为任意类型。如果返回多个值,那么result 中保存的是一个元组。
说明:当函数中没有return 语句时,或者省略了return 语句的参数时,将返回None,即返回空值。
……
6.4 变量的作用域
变量的作用域是指程序代码能够访问该变量的区域,如果超出该区域,在访问时就会出现错误。在程序中,一般会根据变量的“有效范围”将变量分为“全局变量”和“局部变量”。
6.4.1 局部变量
局部变量是指在函数内部定义并使用的变量,它只在函数内部有效。即函数内部的名字只在函数运行时才会创建,在函数运行之前或者运行完毕之后,所有的名字就都不存在了。所以,如果在函数外部用函数内部定义的变量,就会出现抛出NameError异常。
6.4.2 全局变量
与局部变量对应,全局变量为能过作用于函数内外的变量。全局变量主要有以下两种情况:
(1)如果一个变量,在函数外定义,那么不仅在函数外可以访问函数,在该函数内输出全局变量message 的值,代码如下:
……
说明:当局部变量与全局变量重名时,对函数体的变量进行赋值后,不影响函数体外的变量。
(2)在函数体内定义,并且使用global关键字修饰后,该变量也就变为全局变量。在函数体外也可以访问到该变量,并且在函数体内还可以对其进行修改。
注意:尽管Python允许全局变量和局部变量重名,但是在实际开发时,不建议这么做,因为这样容易让代码乱,很难分清哪些是全局变量,哪些是局部变量。
6.5 匿名函数(lambda)
匿名函数是指没有名字的函数,应用在需要一个函数,但是有不想费神去命名这个函数的场合。通常情况下,这样的函数只有使用一次。在Python中,使用lambda表达式创建匿名函数,其语法格式如下:
result = lambda [arg1 [,arg2,......,argn]]:expression
参数说明:
- result:用于调用lambda表达式。
- [arg1[,arg2,......,argn]]:可选参数,用于指定传递的参数列表,多个参数间使用逗号“,”分隔。
- expression:必选参数,用于指定一个实现具体功能的表达式。如果有参数,那么在该表达式中将应用这些参数。
注意:使用lambda表达式时,参数可以有多个,用逗号“,”分隔,但是表达式只能有一个,即只能返回一个值。而且也不能出现其他非表达式语句(如for 或 while)。