函数
1.什么是函数:
函数就是执行特定任何以完成特定功能的一段代码
2.为什么需要函数
- 复用代码
- 隐藏实现细节
- 提高可维护性
- 提高可读性便于调试
3.函数的创建和调用
- 函数的创建
- 函数的定义和调用
#函数的创建和调用 def calc_1(): print("这是一个无返回值的函数") calc_1()
- 函数的返回值
- 函数使用return语句返回值。定义函数时是否需要return语句,需要视情况而定
- 如果函数没有return语句,Python将认为函数以return None结束,即返回空
- 函数可以用return语句,返回一个值
- 函数也可以用return语句返回多个值,多个值以元组类型保存
- 当函数返回多个值时,我们可能需要单独对返回值进行处理,那么我们可以不采用元组的形式接受数据,可使用多变量赋值的形式
-
def calc_2(a,b): c = a + b return c x = calc_2(99,1) print(x) def calc_3(a,b): return a,b x = calc_3(1,2) print(x,type(x)) num_1,num_2 = calc_3(8,9) #多变量赋值的形式,接受函数内部传递过来的值,但是变量个数要与返回值个数相同 print(num_1,num_2)
4.函数的参数传递
- 实参和形参
- 实参:调用函数时向其传递实参,根据不同参数类型,将实参的值或引用传递给形参
- 形参:定义函数时,圆括号内的参数为形参。
- 参数的类型可分为固定数据类型:(如整数、浮点数、字符串、元组等)和可变数据类型(如列表、字典、集合等)
- 当参数类型为固定数据类型时,在函数内部直接修改形参的值不会影响实参
-
- 形参和实参的名字可以不相同
def calc_2(a): #函数名括号内的a时形参 a = 3 #当形参的类型为可变数据类型时,在函数内部直接修改值不会影响实参 return a b = 2 c = calc_2(b) #调用函数时,实际传送的b是实参 print(c) #3 print(b) #2 def calc_1(a): #函数名括号内的a时形参 a.append(1) #当形参的类型为可变数据类型时,在函数内部直接修改值会影响实参 return a list_1 = [1,2,3] print(list_1) #[1, 2, 3] b = calc_1(list_1) #调用函数时,实际传送的list_1是实参 print(b) #[1, 2, 3, 1] print(list_1) #[1, 2, 3, 1]
- 函数参数传递的内存分析:
def fun(arg1,arg2): print(arg1) #11 print(arg2) #[22, 33, 44] arg1 = 100 arg2.append(10) n1 = 11 n2 = [22,33,44] print("调用函数之前n1和n2的值:",n1,n2) #调用函数之前n1和n2的值: 11 [22, 33, 44] fun(n1,n2) print("调用函数之后n1和n2的值:",n1,n2) #调用函数之后n1和n2的值: 11 [22, 33, 44, 10]
- 参数类型:Python中,有多种参数类型,包括:
- 位置参数
- 实参和形参的顺序必须严格一致
- 实参和形参的数量必须相同
-
#定义函数 def calc_1(a,b,c): print(a,b,c) #调用函数 calc_1(1,6,5) #calc_1(4,5)
- 默认值参数
- 函数在定义时,给形参设置默认值,只有与默认值不符时,才需要传递实参。
-
#函数的默认参数 def calc_1(a,b = 10): print(a,b) calc_1(10) #10 10 calc_1(10,100) #10 100
- 关键字参数
- 按参数名字传递值的方式
- 关键字参数允许函数调用时参数的顺序与定义时不一致
-
#关键字参数传递 def calc_1(a,b,c): print("a的值为:",a,"b的值为:",b,"c的值为:",c) calc_1(b = 1,c = 2 , a = 3) #a的值为: 3 b的值为: 1 c的值为: 2
- 不定长参数
- 定义函数时,一个函数可以同时有一个个数可变的位置参数和一个个数可变的关键字形参,但是不能同时存在一个以上的个数可变的位置参数或者一个以上的个数可变的关键字形参
- 个数可变的位置参数:定义函数时,无法确定实参的个数时,可使用可变的位置参数。(接收到的实参存储在元组中)
-
#不定长参数 def calc_1(*args): #可变的位置参数 print(args) calc_1(1) #(1,) calc_1(1,2,3) #(1, 2, 3) calc_1(1,5,6,7,8,7) #(1, 5, 6, 7, 8, 7)
-
个数可变的关键字形参:定义函数时,无法事先确定传递的关键字实参的个数时,使用可变的关键形参。(接收到的实参存储在字典中)
-
def fun(**args): #可变的关键字参数 print(args) fun(a = 1) #{'a': 1} fun(a = 1 , b = 2 , c = 3) #{'a': 1, 'b': 2, 'c': 3}
- 位置参数
5.函数的特殊调用,与参数顺序问题
def fun(a,b,c): print(a,b,c) list_1 = [1,4,7] fun(*list_1) #在调用函数时,将列表的每一个元素都转换为位置实参 注意:列表的元素要与形参相同 tuple_1 = [2,5,8] fun(*tuple_1) #在调用函数时,将元组的每一个元素都转换为位置实参 注意:元组的元素要与形参相同
def fun(a,b,c): print(a,b,c) dict_1 = { "a":100, "b":200, "c":300 } fun(**dict_1) #在调用函数时,将字典的每键值对都转换为关键字实参 注意:字典的键名要与形参名相同
def fun(a,b,c,d): print(a,b,c,d) fun(10,20,30,40) #位置参数 fun(a=10,c=20,b=60,d=100) #关键字参数 fun(10,40,c=20,d=20) #前面使用位置参数,后面使用关键字参数。注意,不能将位置调换 def fun1(a,b,*,c,d): #规定*号后面的形参,必须使用关键字参数 print(a,b,c,d) #fun1(10,20,30,40) #位置参数,报错,因为c和d没有使用关键字形参 fun1(a=10,c=20,b=60,d=100) #关键字参数 fun1(10,40,c=20,d=20) #前面使用位置参数,后面使用关键字参数。注意,不能将位置调换
''' 定义函数时:形参的顺序问题 ''' def fun1(a,b,*,c,d,**kwargs): pass def fun2(*args,**kwargs): pass def fun1(a,b=10,*args,**kwargs): pass
6.变量的作用域
- 程序代码能够访问该变量的区域
- 根据变量的有效范围,可以分为
- 全局变量:函数体外定义的变量,可作用于函数内外。
-
name = "Python" #变量name为全局变量,所以这个变量在函数体的内外都可以使用 print("这是是函数体外的输出:",name) def fun1(): print("这里是函数体内的输出",name) fun1()
#由下列代码可见,函数体内的修改全局变量的值,只能在函数体内使用,不能作用于函数体外。 name = "Python" #变量name为全局变量,所以这个变量在函数体的内外都可以使用 print("这是是函数体外的输出1:",name) #这是是函数体外的输出1: Python def fun1(): #print("这里是函数体内的输出1",name) #此行代码会报错,因为函数体内有修改全局变量的值 name = "HelloWorld" #当我们使用这行代码是,表示:定义了一个局部变量,因此上面这行代码会报错。 print("这是是函数体内的输出2",name) #这是是函数体内的输出2 HelloWorld fun1() print("这是是函数体外的输出2",name) #这是是函数体外的输出2 Python
- 局部变量:在函数内定义的并使用的变量,只在函数内部有效,局部变量在使用global声明,这个变量就会成全局变量。
-
#局部变量 def fun(a,b): c = a + b #c是在函数内部声明的变量,所以c为局部变量;a b 也属于局部变量。作用范围仅限函数内部。 return c print(fun(10,20)) #print(a,b,c) #代码会报错,因为abc都属于局部变量,作用范围都值在函数内部。
#global:在函数体内声明全局变量 num = 1 def fun(): global num #函数内部定义的变量,是局部变量,在使用global声明时,这个变量就变成了全局变量 global num1 num = 2 num1 = 3 print("fun函数体内的num的值:",num) print("fun函数体内的num1的值:",num1) fun() print("fun函数体外的num的值:", num) print("fun1函数体外的num1的值:", num1)
#nonlocal:在函数体内嵌套一个函数,要修改嵌套函数中变量的作用域,可使用nonlocal关键字 def fun1(): num1 = 1 def fun2(): nonlocal num1 #nonlocal num2 #此处会报错,因为外变量中没有num2 global num3 #也可以在嵌套函数的内部声明全局变量 num1 = 2 num3 = 30 print("这是fun2中的num1",num1) print("这是fun2中的num3",num3) fun2() print("这是fun1中的num1",num1) print("这是fun1中的num3",num3) fun1() #print("这是函数外的num1",num1) #此处代码会报错,因为nonlocal不能将局部变量变为全局变量 print("这是函数外的num3",num3)
7.递归函数
- 什么是递归函数:
- 如果在一个函数的函数体内调用了该函数本身,这个函数就成为递归函数。
-
def fun(): print("这是一个递归函数") return fun() fun()
- 递归的组成部分
- 递归调用与递归终止条件
- 递归调用过程
- 每递归调用一次函数,都会在栈内存分配一个栈帧
- 每执行完一次函数,都会释放相应的空间
递归的优缺点
- 缺点:占用内存多,效率低下
- 优点:思路和代码简单
- 例题:6的阶乘
-
-
def fun(num): if num==1: return 1 #出口 else: return num * fun(num - 1) #fun函数内调用fun print(fun(6)) #720
-
- 斐波那契数列的练习:
-
#斐波那契数列 1 1 2 3 5 8 13 ··········· def fun(num): if num == 1: return 1 elif num == 2: return 1 else: return fun(num-1)+fun(num-2) a = 6 print("斐波那契数列第",a,"位的数值为:",fun(a)) #斐波那契数列第 6 位的数值为: 8 #输出斐波那契数列每一项的值 for x in range(1,a+1): print(fun(x),end=" ") #1 1 2 3 5 8
-
8.总结:
拆包
- 在调用拥有多值参数的函数时,如果希望元组传递给*args,字典传递给**kwargs,就可以使用拆包
- 在调用时,给实参加上分别加上*和**
-
a = (1,2,3,4,5) b = {"name":"zms","age":35} fun(a,b) #运行结果 ((1, 2, 3, 4, 5), {'name': 'zms', 'age': 35}) {} #拆包 fun(*a,**b) # a会拆成5个位置实参:1,2,3,4,5 b会拆成两个关键字参数: "name":"zms","age":35
#运行结果 (1, 2, 3, 4, 5) {'name': 'zms', 'age': 35}
匿名函数:
- 语法格式:
函数名 = lambda [参数列表]:表达式
-
代码演示:
1 # 无参数匿名函数 2 import random 3 4 sum = lambda :print("python") 5 sum() #python 6 7 random_lambda = lambda : random.randint(1,3) 8 print( random_lambda() ) # 1 2 3 9 10 # 匿名函数 11 sum = lambda args1,args2=10: args1+args2 12 print(sum(20)) # 30 13 print(sum(20,30)) # 50 14 15 # 利用三元表达式 16 get_odd_even = lambda x: "%s是偶数"%x if x%2==0 else "%s是奇数"%x 17 print(get_odd_even(8)) #8是偶数 18 print(get_odd_even(9)) #9是奇数 19 20 # map函数 与 函数结合 21 def add(x): 22 return x**2 23 mobj = map(add, [1,2,3,4]) 24 print( list(mobj) ) # [1, 4, 9, 16] 25 #使用 匿名函数 与 map函数 结合 26 mobj_1 = map(lambda x: x**2, [1,2,3,4,5]) 27 print( list(mobj_1) ) #[1, 4, 9, 16, 25]
这个知识点在面试题中出现的概率比较高,但真正实际开发中用的比较少。
def func(a1,a2=18):
print(a1,a2)
原理:Python在创建函数(未执行)时,如果发现函数的参数中有默认值,则在函数内部会创建一块区域并维护这个默认值。
执行函数未传值时,则让a2指向 函数维护的那个值的地址。
func("root")
执行函数传值时,则让a2指向新传入的值的地址。
func("admin",20)
在特定情况【默认参数的值是可变类型 list/dict/set】 & 【函数内部会修改这个值】下,参数的默认值 有坑 。
-
坑
# 在函数内存中会维护一块区域存储 [1,2,666,666,666] 100010001 def func(a1,a2=[1,2]): a2.append(666) print(a1,a2) # a1=100 # a2 -> 100010001 func(100) # 100 [1,2,666] # a1=200 # a2 -> 100010001 func(200) # 200 [1,2,666,666] # a1=99 # a2 -> 1111111101 func(99,[77,88]) # 66 [177,88,666] # a1=300 # a2 -> 100010001 func(300) # 300 [1,2,666,666,666]
-
大坑
# 在内部会维护一块区域存储 [1, 2, 10, 20,40 ] ,内存地址 1010101010 def func(a1, a2=[1, 2]): a2.append(a1) return a2 # a1=10 # a2 -> 1010101010 # v1 -> 1010101010 v1 = func(10) print(v1) # [1, 2, 10] # a1=20 # a2 -> 1010101010 # v2 -> 1010101010 v2 = func(20) print(v2) # [1, 2, 10, 20 ] # a1=30 # a2 -> 11111111111 [11, 22,30] # v3 -> 11111111111 v3 = func(30, [11, 22]) print(v3) # [11, 22,30] # a1=40 # a2 -> 1010101010 # v4 -> 1010101010 v4 = func(40) print(v4) # [1, 2, 10, 20,40 ]
-
深坑
# 内存中创建空间存储 [1, 2, 10, 20, 40] 地址:1010101010 def func(a1, a2=[1, 2]): a2.append(a1) return a2 # a1=10 # a2 -> 1010101010 # v1 -> 1010101010 v1 = func(10) # a1=20 # a2 -> 1010101010 # v2 -> 1010101010 v2 = func(20) # a1=30 # a2 -> 11111111111 [11,22,30] # v3 -> 11111111111 v3 = func(30, [11, 22]) # a1=40 # a2 -> 1010101010 # v4 -> 1010101010 v4 = func(40) print(v1) # [1, 2, 10, 20, 40] print(v2) # [1, 2, 10, 20, 40] print(v3) # [11,22,30] print(v4) # [1, 2, 10, 20, 40]