python3基础篇(七)——函数
python3基础篇(七)——函数
前言:
1 阅读这篇文章我能学到什么?
这篇文章将为你详细介绍python3函数的用法,将会非常详细。
——如果你觉得这是一篇不错的文章,希望你能给一个小小的赞,感谢您的支持。
目录
程序的函数概念就类似数学上的函数概念。按照一定语法结构能完成特定的功能的代码段,函数可以具有输入和输出(严格来说函数必须具有输出,没有任何输出的函数是没有意义的,只是语法结构上满足函数定义)。
1 定义函数
python3以def
关键字表示定义函数,随后自定义一个函数名,需要注意函数名在作用域内不能同名(后面补一章讲下python3的作用域吧,这里不懂先不必纠结)。函数名之后()
内为参数列表,参数列表可以为空,它表示传入函数的参数。最后不要忘了:
符号。另起一行的是可选的“函数说明字符串”,用于对函数简要描述,可省略不写。函数体必须比函数名至少缩进一个空格或table。与c/c++类似,函数可以搭配return
关键词结束函数并返回一个值给调用方,当函数不需要返回值时可以返回None
,也可以省略None
,甚至可以省略return
。
语法结构:
def FunctionName(ParameterList):
"Information"
FunctionBody
代码示例:
def Fun1(): #无参数函数
"函数描述信息" #这不是必须的,看个人喜好,我个人是不喜欢在这里写函数注释
print("Fun1")
#无返回值,省略return
def Fun2(a): #单参数参数列表
print("Fun2: %d" % (a))
return None #无返回值,返回None相当于无返回值
def Fun3(a, b): #多个参数的参数列表
print("Fun3: %d" % (a + b))
return #无返回值
#可以看到上面三种函数返回值都是等同的,返回值都是None
print(Fun1())
print(Fun2(1))
print(Fun3(1, 2))
运行结果:
Fun1
None
Fun2: 1
None
Fun3: 3
None
上面主要介绍了函数的结构,下面尝试写一些功能性的函数。
找出一串数字中最大的数:
代码示例:
def FindMaxNum(List):
i = 0
MaxNum = List[0] #放入列表第一个数
for i in range(len(List)):
if List[i] > MaxNum:
MaxNum = List[i]
return MaxNum
ListNum = [3, 2, 5, 0, 2]
print(FindMaxNum(ListNum))
运行结果:
5
上面这个函数实现了输出列表最大元素的功能。结构上具有函数名、参数列表、函数主体,功能上独立且具有封装性,算是一个标准的功能函数了。
另外还需要提的一点,python3的函数可以分为两类, 内置函数 和 自定义函数 。内置函数即python3自带的,由python3设计者们已经为我们实现的功能函数,比如“print”和元组、列表、字典等最基本的操作函数。因为这些函数是最常用的,由python3设计者们统一替我们实现好可以避免我们重复“造轮子”,另外这些函数的可靠性也非常有保证(出了Bug别多想了,一定是你自己的问题)。自定义函数包括我们自己实现的函数,也包括别人提供的库函数,它们并不是python3语言原生自带的。
2 函数调用
2.1 如何调用一个函数
在定义函数时,函数结构要求具有函数名(在作用域内名称唯一),具有参数列表(可以为空)。那么在调用函数时,就像每个人都有名字一样我们需要写出被调函数的函数名,()
内需要相应给出被调函数实现功能时需要我们提供的参数,最后具有返回值的函数根据需要决定是否接收其返回值。
代码示例:
print("Test") #函数调用,写明要调用的函数名,传递的参数列表。未使用返回值
运行结果:
Test
2.2 函数参数与可变及不可变类型
2.2.1 可变及不可变类型
在介绍数据类型的时候我们说过,python3的变量是没有类型的,它可以存储任意类型的值,需要分类型的是被存储的数据。下面我写一段代码详细看一下:
代码示例:
#小数字缓存池-5~256内的整数变量,值不同地址不同,单值相同时地址相同
Number = 1 #定义变量Number,给其赋值整数1
print(id(Number)) #输出变量地址
Number = 2 #改变其值,变量的地址发生改变,这时的Number已不同于之前
print(id(Number))
print("------------------------")
#非缓存池内的数
Number = 0.1 #定义变量Number,给其赋值浮点数1.0
print(id(Number))
Number = 0.2 #改变变量的值,变量的地址也发生改变,变量已经不同于之前
print(id(Number))
print("------------------------")
运行结果:
140720811398816
140720811398848
------------------------
2445246815632
2445246366960
------------------------
从这段代码我们可以看出,不同于c/c++的变量。在有指针概念的编程语言中,变量定义好后其就具有了确定的地址,不会因为变量中存储的值改变而引起变量地址的改变。而在python3中变量中的值改变可能会引起变量地址改变。我们把这种改变值能引起变量地址改变的数据类型称为 不可变类型 ,而改变值不会引起变量地址改变的数据类型称为 可变类型。这么理解起来有些难懂。
通俗点说,当一个变量中存储的是不可变类型对象时(比如数值型),改变这个变量的值会使得变量地址发生改变,可以理解为这个变量已经不是“原来”的变量了,虽然同名。所以你想做到让原变量的值改变是不行的,也即不能改变原变量的值。而可变类型比如列表,你改变其元素的值,存储该列表变量仍然是原变量(地址不变)。
那么python3中有哪些是可变或不可变类型呢?
代码示例:
#数值类型是不可变类型
Number = 0
print(id(Number))
Number = 1
print(id(Number))
print("-----------------------------------")
#字符串类型是不可变类型
String = "a"
print(id(String))
String = "b"
print(id(String))
print("-----------------------------------")
#元组元素不能修改,所以谈不上可变还是不可变
Tuple = (1, 2)
print(id(Tuple))
Tuple = (1, 3) #不过给元组变量赋值其他元组时地址会变
print(id(Tuple))
print("-----------------------------------")
#列表类型是可变类型
List = [1, 2]
print(id(List))
List = [1, 3] #给列表变量赋值其他列表时地址会变
print(id(List))
List[1] = 4 #改变列表中元素时,列表变量地址不会变
print(id(List))
print(List)
print("-----------------------------------")
#集合类型是可变类型
Set = {1, 2}
print(id(Set))
Set = {1, 3}
print(id(Set)) #给集合变量赋值其他集合时地址会变
Set.add(4) #改变集合中元素时,集合变量地址不会变
print(id(Set))
print(Set)
print("-----------------------------------")
#字典类型是可变类型
Direction = {1:"1", 2:"2"}
print(id(Direction))
Direction = {1:"1", 3:"3"} #给字典变量赋值其他字典时地址会变
print(id(Direction))
Direction[3] = 4 #改变字典中元素时,字典变量地址不会变
print(id(Direction))
print(Direction)
print("-----------------------------------")
运行结果:
140720178583168
140720178583200
-----------------------------------
2669025674736
2669025613808
-----------------------------------
2669025805504
2669026779712
-----------------------------------
2669025812736
2669027094464
2669027094464
[1, 4]
-----------------------------------
2669026087648
2669027131680
2669027131680
{1, 3, 4}
-----------------------------------
2669025758592
2669025758656
2669025758656
{1: '1', 3: 4}
-----------------------------------
从以上代码示例可以总结出,列表、集合、字典类型属于可变类型,改变其元素的值变量地址不会变。而其他类型改变值时,或变量被赋值其他类型时地址都将改变。
我们讨论了半天可变和不可变类型,到底有啥实际运用呢?主要在函数的传参时会涉及到这个问题,请继续往下看。
2.2.2 可变及不可变类型函数参数
我们知道c/c++中函数参数传递,传递的只是值,改变函数内的变量值不会引起外部变量的值改变,除非将变量的指针传递进函数内。在python3里,当参数传递进函数的是不可变类型时,改变函数内变量的值不会引起函数外变量值得改变。当传递的是可变类型时,在函数内修改,函数外变量的值也会改变,因为它们实际是同一个变量。
代码示例:
def ModifyNum(Num):
Num += 1 #在函数内修改变量值
print(Num)
Num = 0
ModifyNum(Num)
print(Num) #函数外值不改变
print("-----------------------------")
def ModifyString(String):
String += "1" #在函数内修改变量值
print(String)
String = "0"
ModifyString(String)
print(String) #函数外值不改变
print("-----------------------------")
def ModifyList(List):
List[0] = 1 #在函数内修改变量值
print(List)
List = [0]
ModifyList(List)
print(List) #函数外变量值也改变
print("-----------------------------")
def ModifySet(Set):
Set.add(1) #在函数内修改变量值
print(Set)
Set = {0}
ModifySet(Set)
print(Set) #函数外变量值也改变
print("-----------------------------")
def ModifyDirection(Direction):
Direction[0] = "1" #在函数内改变变量值
print(Direction)
Direction = {0:"0"}
ModifyDirection(Direction)
print(Direction) #函数外变量值也改变
print("-----------------------------")
运行结果:
1
0
-----------------------------
01
0
-----------------------------
[1]
[1]
-----------------------------
{0, 1}
{0, 1}
-----------------------------
{0: '1'}
{0: '1'}
-----------------------------
函数参数传递时,不可变类型在函数内修改不会改变函数外变量的值,而可变类型在函数内修改值,函数外变量值也会相应改变。调用函数了解特性这个很重要。
2.2.3 几种函数参数
函数参数在python3里可以分为4类:
- 必选参数:调用时必须按照定义时的参数列表顺序和数量写,不能省略和乱序。
- 关键字参数:允许不按定义是的参数列表顺序写,但是必须写明是传递给哪个参数。
- 可选参数:参数在函数定义时参数列表里给出对应参数的默认值,调用时可以重新传递值也可省略,省略时传递的为定义时的默认值。
- 不定长参数:允许函数调用时传递比定义时参数列表里更多数量的参数,必须按照一定语法规则写。(我们常用的print函数就是一个不定长参数函数。)
2.2.3.1 必选参数
必选参数调用时必须和函数定义时保持一致的顺序和数量。
代码示例:
def Fun1(Parameter1, Parameter2, Parameter3): #必选参数,调用时需要按照其顺序额和数量
print(Parameter1)
print(Parameter2)
print(Parameter3)
Fun1(1, "2", (3,)) #依次传入参数Number、String、Tuple
运行结果:
1
2
(3,)
2.2.3.2 关键字参数
若在函数调用时明确写明是给哪个参数传递值,调用时就可以不按定义时的顺序,这样的参数我们称为关键字参数。哪个参数成为关键字参数取决于调用时指明了哪些参数的传值。
代码示例:
def Fun1(Parameter1, Parameter2, Parameter3): #确定参数,调用时需要按照其顺序额和数量
print(Parameter1)
print(Parameter2)
print(Parameter3)
Fun1(1, Parameter3 = (3,), Parameter2 = "2")
print("--------------------------------------------------------")
Fun1(1, "2", Parameter3 = (3,))
print("--------------------------------------------------------")
Fun1(Parameter2 = "2", Parameter1 = 1, Parameter3 = (3,))
#Fun1(1, Parameter2 = "2", (3,)) #error
#Fun1(Parameter1 = 1, "2", (3,)) #error
运行结果:
1
2
(3,)
--------------------------------------------------------
1
2
(3,)
--------------------------------------------------------
1
2
(3,)
使用关键字调用函数可以不按函数定义时的顺序。 调用函数时你可以把所有参数都写成关键字参数,也可以一部分按必选参数调用,一部分按关键字参数调用。注意 混用必选参数和关键字参数时,必选参数必须在前面且符合函数定义时的顺序(一定是前几个),从第一个被作为关键字参数开始之后的所有参数都必须是关键字参数,必须按关键字参数形式调用。比如上面的例子,Parameter1
作为确定参数,而Parameter2
被作为关键字参数,那么Parameter3
也就是关键字参数了。还需注意的是,函数调用时必选参数必须写在关键字参数前面,不能再关键字参数后出现必选参数的形式。
代码示例:
def Fun1(a, b, c):
print(a, b, c)
Fun1(1, 2, 3)
Fun1(a = 1, b = 2, c = 3)
Fun1(1, b = 2, c = 3)
Fun1(1, 2, c = 3)
#Fun1(a = 1, b = 2, 3) #error a参数以关键字参数形式,则b和c也必须是关键字参数形式
#Fun1(a = 1, 2, 3) #error a参数以关键字参数形式,则b和c也必须是关键字参数形式
#Fun1(1, b = 2, 3) #error b参数以关键字参数形式,则c也必须是关键字参数形式
#Fun1(c = 3, 1, 2) #error 虽然c参数是以关键字参数形式,不要求a和b也是关键字参数形式,但是必选参数必须写在关键字参数前面
#以下同样是错的
#Fun1(3, a = 1, b = 2)
##Fun1(2, 3, a = 1)
#Fun1(3, 1, b = 2)
运行结果:
1 2 3
1 2 3
1 2 3
1 2 3
2.2.3.3 可选参数
在定义函数时,可以给参数赋值一个默认值。在函数调用时若未给带默认值的参数传递值时,会使用这个默认值。
代码示例:
def Fun1(Parameter1 = 1, Parameter2 = 2, Parameter3 = 3):
print(Parameter1, Parameter2, Parameter3)
Fun1() #所有参数使用默认值
Fun1(4) #给Parameter1传递值
Fun1(4, 5) #给Parameter1和Parameter2传递值
Fun1(4, 5, 6) #给Parameter1、Parameter2、Parameter3传递值
#Fun1(_, _, 6) #error python不能像lua那样用_表示参数使用默认值
print("-----------------------------------------")
def Fun2(Parameter1, Parameter2 = 2): #有必选参数和
print(Parameter1, Parameter2)
Fun2(1)
print("-----------------------------------------")
#def Fun3(Parameter1 = 1, Parameter2): #error 之后必须也都是可选参数
x = 1 #函数外变量
def Fun4(Parameter1 = x): #给的是变量值
print(Parameter1)
x = 2 #修改x的值
Fun4() #的值是创建函数时变量的值,后续修改x的值不会改变可选参数的默认值
print("-----------------------------------------")
运行结果:
1 2 3
4 2 3
4 5 3
4 5 6
-----------------------------------------
1 2
-----------------------------------------
1
-----------------------------------------
定义时需要 注意 函数参数列表中可选参数之后也都必须是可选参数,不能在可选参数之后又定义必选参数。可选参数的默认值一般用常量,也可以用变量。变量的值运行中是可以变的,而可选参数只会在构建函数时取一次变量的值。python3的可选参数不能像Lua那样使用_
符号,比如print(_, 1)
表示第一个参数使用默认值,第二个参数进行值传递。在pathon3中要对后面的可选参数传递值必须也对前面的可选参数给出具体的传递至,这点对脚本语言来说确实不够方便啊。
2.2.3.3 不定长参数
我们熟悉的print
函数就是不定长参数的函数。如果要实现一个函数去计算n个数的和,可以有两种方式。一种是将n个数以元组列表等形式传递进函数,另外一种就是使用不定长参数。python3中没有指针的概念,如果在函数定义的参数列表中参数名前加上*
就表示这是不定长参数,不定长的参数会以元组形式传入。如果加的是**
也是不定长参数,不同的是不定长参数以字典形式传入。所以python3的不定长参数有两种形式,元组或字典形式,取决于所用的*
号个数。 注意 不定长参数一般写成函数参数列表的最后一个参数,因为后面所有传入的参数值都被当做不定长参数里的值,如果不定长参数后面还定义了其他参数,调用时必须按关键字参数形式调用。 注意 不定长参数的最小参数个数可以是0,这是对应的元组是空元组,对应的字典是空字典。
代码示例:
def add(Size, *Numbers): #传入求和的个数和不定长的数据
Sum = 0
for i in range(Size):
Sum += Numbers[i]
print(Size)
print(Numbers) #不定长参数最后以元组形式传入
return Sum
print(add(3, 1, 2, 3))
print("---------------------------------------")
def Fun1(*UnsetTuple):
print(UnsetTuple)
def Fun2(**UnsetDirection):
print(UnsetDirection)
Fun1() #参数个数为0,可变参数为空元组
Fun2() #参数个数为0,可变参数为空字典
Fun1(1, 2, 3)
Fun2(a=1, b=2, c=3) #这时候的字典键値不能写数值型
print("---------------------------------------")
def Fun3(*UnsetTuple, Size): #不定长参数后还定义了其他参数
print(UnsetTuple)
Fun3(1, 2, Size = 2) #调用时Size参数必须以关键字参数形式调用
#Fun3(1, 2, 3) #error 会被认为没给Size参数赋值
#Fun3(Size = 2, 1, 2) #error 前面我们说过关键字参数之后的参数也必须以关键字参数形式赋值
print("---------------------------------------")
运行结果:
3
(1, 2, 3)
6
---------------------------------------
()
{}
(1, 2, 3)
{'a': 1, 'b': 2, 'c': 3}
---------------------------------------
(1, 2)
可变参数一般都写作最后一个参数,否则是不太规范的写法。例子add
函数的Size
参数实际在python3中是没必要的,因为元组或字典有有内置的函数可以得到元素个数。*
或**
号与函数名之间可以加入空格,这个看个人书写习惯,我个人不推荐加空格,因为这看上去像是求积或求幂。
2.2.3.4 强制必选参数
这是python v3.8版本新加的特性。它不是具体的带值参数,而是对参数调用时的书写规则进行一些强制限制。
2.2.3.4.1 /参数
函数定义时,参数列表中/
参数之前的参数在调用时必须以必选参数形式。/
符号本身并不接受值,它只是限制了函数调用时参数的书写规则。参数传值时会跳过/
号。
代码示例:
def Fun(a, b, /, c, d):
print(a, b, c, d)
Fun(1, 2, 3, 4)
#Fun(a = 1, b = 2, 3, 4) #error 前面讲过,关键词参数之后的参数也必须按关键词参数形式
#Fun(a = 1, b = 2, c = 3, d = 4) #error /号前面的参数必须以必选参数形式
Fun(1, 2, 3, d = 4) #/号之后的参数按正常的可以是必选参数也可以关键字参数等
Fun(1, 2, c = 3, d = 4)
运行结果:
1 2 3 4
1 2 3 4
1 2 3 4
2.2.3.4.2 *参数
与/
不同的是*
作用于其后的参数。*
之后的参数调用时必须以关键字参数形式。
代码示例:
def Fun(a, b, *, c, d):
print(a, b, c, d)
Fun(1, 2, c = 3, d = 4)
#Fun(1, 2, 3, 4) #error *号之后的参数必须以关键字参数形式
#Fun(1, 2, 3, d = 4) #error
运行结果:
1 2 3 4
所以/
和*
参数共同作用的结果为:
代码示例:
def Fun1(a, b, /, c, d, *, e, f): #/号前必须是必选参数,*号后必须是关键字参数
print(a, b, c, d, e, f)
Fun1(1, 2, 3, d = 4, e = 5, f = 6) #a和b必须是必选参数,d和e可以是必选参数也可以是关键字参数,e和f必须是关键字参数
'''
def Fun2(a, b, *, c, d, /, e, f): #error 提示/符号无效
print(a, b, c, d, e, f)
'''
运行结果:
1 2 3 4 5 6
因为/
的作用域是往前的,*
的作用域是往后的,它们要求的规则不同。在使用时/
必须在*
前,否则语法错误。
2.3 递归调用
递归是一种函数的嵌套调用,函数自己调用自己。可以是一个函数循环调用自己,也可以是多个函数之间互相调用但必须形成循环。
代码示例:
def Fun1(n):
print(n)
if n > 0:
return Fun1(n - 1)
else:
return n
def Fun2(n):
print(n)
if n > 0:
return Fun3(n - 1)
else:
return n
def Fun3(n):
print(n)
if n > 0:
return Fun2(n - 0.5)
else:
return n
Fun1(5) #递归调用
print("------------------------")
Fun2(3) #递归调用
运行结果:
5
4
3
2
1
0
------------------------
3
2
1.5
0.5
0.0
python3是不支持尾调用的。
3 匿名函数
匿名函数与普通函数不同。
- 使用关键字
lambda
而不是def
定义。- 普通函数的函数体可以是一个语句也可以是多个语句的代码块,而匿名函数的函数主体只能是一个语句。
- 普通函数调用的结果取决于
return
关键字返回的值,而匿名函数的结果为这一个语句的计算结果值。- 和普通函数一样匿名函数也有自己的命名空间,也分内部和外部变量。
- 匿名函数没有函数名,通过定义时赋值的变量进行调用。可以使用列表将多个匿名函数捆绑在一起,通过列表取访问。
3.1 匿名函数的定义
匿名函数主要用途是将一些常用的表达式简洁的写成匿名函数形式。
代码示例:
Sum = lambda a, b: a + b #a和b是匿名函数的参数,a+b是匿名函数的表达式,结果即为表达式结果
Sum2 = Sum #别名
print(Sum(1, 2)) #调用结果是表达式结果
print(Sum2(1, 2))
print("-------------------------------------------------")
c = 3 #全局变量
d = 4
def Fun1(d):
print(c, d) #普通函数访问全局变量,普通函数局部变量与全局同名时使用局部变量
Fun2 = lambda d: print(c, d) #匿名函数访问全局变量, 匿名函数也有自己的命名空间,局部变量与全局变量同名时使用局部变量
Fun1(5)
Fun2(5)
print("-------------------------------------------------")
#Fun3 = lambda : e = 6 #error 匿名函数函数主体不能定义变量
MaxNum = lambda a, b: a if a > b else b #三元运算
print(MaxNum(1, 2))
print("-------------------------------------------------")
#Fun3 = lambda a, b: a - b; a + b; #error 匿名函数的函数主体只能有一个语句
Fun4 = lambda : print("Test")
print(Fun4()) #调用结果是print函数的返回值
print("-------------------------------------------------")
运行结果:
3
3
-------------------------------------------------
3 5
3 5
-------------------------------------------------
2
-------------------------------------------------
Test
None
-------------------------------------------------
一些地方说匿名函数不能访问全局,实测是可以访问的。要注意匿名函数的函数主体只能是一个语句,如果这个语句是表达式那匿名函数的结果就是表达式的结果,如果是调用其他函数,那结果是被调用函数的结果。匿名函数时通过定义时的赋值的变脸来访问的,这个变量也可以是列表,下面展示一个列表内捆绑多个匿名函数。
代码示例:
FunList = [lambda a, b: a + b, #列表中嵌套进多个匿名函数
lambda a, b: a - b,
lambda a, b: a * b,
lambda a, b: a / b,
]
print(FunList[0](1, 2)) #通过列表访问匿名函数,单独一个匿名函数是没有函数名去访问的
print(FunList[1](1, 2))
print(FunList[2](1, 2))
print(FunList[3](1, 2))
运行结果:
3
-1
2
0.5
匿名函数的主要作用就是将一些常用的表达式进行简单的封装,增加代码的重用性,要实现复杂的逻辑请使用普通函数。
3.2 匿名函数的参数
前面我们已经介绍过普通函数的参数了,匿名函数的参数用法与其完全一致。
代码示例:
Fun1 = lambda a, b, c: print(a, b, c) #必选参数
Fun2 = lambda a, b = 2, c = 3: print(a, b, c) #含可选参数
Fun3 = lambda *a: print(a) #变长参数(元组形式)
Fun4 = lambda **a: print(a) #变长参数(字典形式)
Fun1(1, 2, 3)
Fun2(1)
Fun1(a = 1, c = 3, b = 2) #关键字参数
Fun3(1, 2, 3)
Fun4(a = 1, b = 2)
代码示例:
1 2 3
1 2 3
1 2 3
(1, 2, 3)
{'a': 1, 'b': 2}