这个水晶球就是“函数”。
函数就是执行特定任和以完成特定功能的一段代码。函数的优点包括:复用代码、隐藏实现细节、提高可维护性、提高可读性便于调试等。函数的创建和调用格式如下:
'''函数的创建格式'''
def 函数名([形式参数]):
函数体
[return xxx]
'''函数的调用格式'''
函数名([实际参数])
注:要先定义函数,才能进行调用。
下面是代码示例:
def cal(a,b):
c=a+b
return c
result = cal(10, 20)
print(result)
30
函数调用的参数传递主要有两种方式:
- 按照位置传递实参:根据 形参对应的位置 进行实参传递,顺序不可改变。
- 按照关键字传递实参:根据 形参名称 进行实参传递,顺序可以改变。
下面是代码示例:
def cal(a,b):
c=a+b
return c
print('位置传递:', cal(10, 20))
print('关键字传递:', cal(b=20,a=100)) # 左侧的变量名称称为关键字参数
30
120
下面以一个程序来举例,说明函数调用时参数传递的内存分析图。
def fun(arg1, arg2):
print('arg1=', id(arg1), arg1)
print('arg2=', id(arg2), arg2)
arg1 = 100
arg2.append(10)
print('arg1=', id(arg1), arg1)
print('arg2=', id(arg2), arg2)
# return
n1 = 11
n2 = [22, 33, 44]
print('调用函数前:', id(n1), n1)
print('调用函数前:', id(n2), n2)
print('---调用函数中----')
fun(n1, n2)
print('----------------')
print('调用函数后:', id(n1), n1)
print('调用函数后:', id(n2), n2)
上面这个程序在运行的过程中,其内存分析图如下:
最终其运行结果为:
调用函数前: 140718152341472 11
调用函数前: 2535859027008 [22, 33, 44]
---调用函数中----
arg1= 140718152341472 11
arg2= 2535859027008 [22, 33, 44]
arg1= 140718152344320 100
arg2= 2535859027008 [22, 33, 44, 10]
----------------
调用函数后: 140718152341472 11
调用函数后: 2535859027008 [22, 33, 44, 10]
即,在函数调用过程中,进行参数的传递:
- 如果是不可变对象,在函数体的修改不会影响实参的值。
- 如果是可变对象,在函数体的修改会影响实参的值。
函数的返回值有如下情况:
- 若函数没有返回值,
return
可以省略不写。- 若函数的返回值只有1个,直接返回其类型。
- 若函数的返回值有多个,返回的结果为元组。
下面是代码示例:
def fun(num):
odd = [] # 定义空列表
even = []
for i in num:
if i%2:
odd.append(i)
else:
even.append(i)
return odd, even
result = fun(range(-1,-10,-1))
print(result)
print('奇数列表为:', result[0])
print('偶数列表为:', result[1])
([-1, -3, -5, -7, -9], [-2, -4, -6, -8])
奇数列表为: [-1, -3, -5, -7, -9]
偶数列表为: [-2, -4, -6, -8]
函数的参数定义除了可以直接声明变量,还有三种特殊方式:
- 默认值参数:函数定义时给形参设置默认值,只有与默认值不符的时候才需要传递实参。
- 个数可变的位置参数:适用于无法确定要传递的位置实参的个数时,使用
*
定义,类型为元组- 个数可变的关键字参数:适用于无法确定要传递的关键字实参的个数时,使用
**
进行定义,类型为字典。注:对于第二种和第三种参数,只能定义一个。
注:若同时都有定义,第二种必须放在第三种之前(两个星号的必须在前面)。
下面是代码示例:
def fun1(a, b=10): # 默认值参数
print(a, b)
return
def fun2(*args): # 可变的位置参数
print(args)
def fun3(**args): # 可变的位置参数
print(args)
def fun4(*args1, **args2): # 不会报错
pass
# def fun5(**args1, *args2): # 会报错,SyntaxError: invalid syntax
# pass
print('---------默认值参数---------')
print('传递1个参数:', fun1(100))
print('传递2个参数:', fun1(100, 20))
print('-----个数可变的位置参数------')
fun2(10)
fun2(10, 20)
fun2(10, 20, 30)
print('-----个数可变的关键字参数-----')
fun3(a=10)
fun3(a=10, b=20)
fun3(a=10, b=20, c=30)
---------默认值参数---------
100 10
传递1个参数: None
100 20
传递2个参数: None
-----个数可变的位置参数------
(10,)
(10, 20)
(10, 20, 30)
-----个数可变的关键字参数-----
{'a': 10}
{'a': 10, 'b': 20}
{'a': 10, 'b': 20, 'c': 30}
同时从上面的代码结果中可以看出,一条语句包含函数时,会首先运行函数体,再运行该条语句。
最后总结一下函数的参数类型:
分类 | 参数的类型 | 备注 |
---|---|---|
函数的调用 | 位置实参 | |
将元组、列表、集合的每个元素都转换成位置实参 | 使用* | |
关键字实参 | ||
将字典的所有键值对都转换成关键字实参 | 使用** | |
函数的定义 | 默认值形参 | |
关键字形参 | 使用* | |
个数可变的位置形参 | 使用* | |
个数可变的关键字形参 | 使用** |
下面是函数的调用代码示例:
def fun(a, b, c):
print('a =', a, ' b =', b, ' c =', c)
print('-----函数调用实参的不同方式------')
print('位置实参:', end='')
fun(10, 20, 30) # 位置实参
print('元组、列表、集合转换成位置实参:', end='')
lst = {40, 50, 60} # 元组、列表、集合都可以
fun(*lst) # 调用时将每个元素都转换成 位置实参
print('关键字实参:', end='')
fun(a=70, b=80, c=90) # 关键字实参
print('字典转换成关键字实参:', end='')
dic = {'a':100, 'b':110, 'c':130}
fun(**dic) # 调用时将所有键值对都转换成 关键字实参
-----函数调用实参的不同方式------
位置实参:a = 10 b = 20 c = 30
元组、列表、集合转换成位置实参:a = 40 b = 50 c = 60
关键字实参:a = 70 b = 80 c = 90
字典转换成关键字实参:a = 100 b = 110 c = 130
下面是函数的定义代码示例:
def fun1(a, b=10): # a为位置形参,b为默认值形参
print('a =', a, ' b =', b)
def fun2(*args): # 个数可变的位置形参
print(args)
def fun3(**args): # 个数可变的关键字形参
print(args)
def fun4(a,b,c):
print('a =', a, ' b =', b, ' c =', c)
def fun5(a,*,b,c): # 星号后面都必须使用关键字实参
print('a =', a, ' b =', b, ' c =', c)
print('-------演示fun1()---------')
print('-------演示fun2()---------')
fun2(10, 20, 30)
print('-------演示fun3()---------')
fun3(a=10, b=20)
print('-------演示fun4()---------')
fun4(10, 20, 30) # 位置实参
fun4(a=40, b=50, c=60) # 关键字实参
fun4(40, 50, c=60) # 前两个位置实参,后一个关键字实参
print('-------演示fun5()---------')
# fun5(40, 50, c=60) # 会报错说少一个关键字实参
fun5(40, c=50, b=60)
-------演示fun1()---------
-------演示fun2()---------
(10, 20, 30)
-------演示fun3()---------
{'a': 10, 'b': 20}
-------演示fun4()---------
a = 10 b = 20 c = 30
a = 40 b = 50 c = 60
a = 40 b = 50 c = 60
-------演示fun5()---------
a = 40 b = 60 c = 50
函数定义参数时的顺序(从前到后):位置形参 > 关键字形参、可变数量位置形参 > 可变数量关键字形参。若有一个单独的星号(表明其后面都是关键字形参),则可变数量位置形参一定要提前到该星号之前。
变量的作用域指的是程序代码能访问该变量的区域,在作用域之外调用变量会报错。根据变量的有效范围可分为:
- 局部变量:在函数内定义并使用的变量,只在函数内部有效,局部变量使用global声明,这个变量
就会就成全局变量。- 全局变量:函数体外定义的变量,可作用于函数内外。
在下面这段代码中,c
称为局部变量,变量a, b
作为函数的形参,作用范围也是函数内部,也就是局部变量。于是在函数体外部调用a, b, c
就会报错。
def fun(a, b):
c = a+b # c称为局部变量
print(c)
# print(a) # 会报错
在下面这段代码中,name
是全局变量,在函数体内部和外部都可以使用,所以会打印两次。(C/C++不是这样的!!)
def fun2():
print(name)
name = '张三'
print(name)
fun2()
张三
张三
下面这段代码中,age
本来作为局部变量,只能作用于函数体内部,但其使用 global
声明,就成为全局变量,在函数体外部也可以调用。
def fun3():
global note
note = '去码头整点薯条'
print(note)
fun3()
print(note)
去码头整点薯条
去码头整点薯条
如果在一个函数的函数体内调用了该函数本身,这个函数就称为递归函数。递归(递推、回归)由 递归调用 与 递归终止条件 两部分组成。进行递归时,每递归调用一次函数,都会在栈内存分配一个栈帧,当执行完本次函数,就会释放相应的空间只返回结果,如此循环直到完成整个递归。递归的优缺点如下:
缺点:占用内存多,效率低下。
优点:思路和代码简单。
下面是代码示例:
'''递归计算阶乘'''
def fac(n):
if(n==1):
return 1
else:
res = n*fac(n-1)
return res
print('4的阶乘为:', fac(4))
4的阶乘为: 24
在调用递归函数的语句打上断点,进入调试模式,然后一直执行步入(step into),便可以在调试(debugger)窗口看到函数fac递推到回归的整个过程。
课堂作业:使用递归完成斐波那契数列的计算。
下面是代码示例:
'''
斐波那契数列:前两项均为1,此后的每一位都是前两项的和。
'''
'''下面这个函数体没有用到递归但是算出来了'''
'''函数体功能:输入序列长度,返回相应长度的斐波那契数列。'''
def fibo_sequ(mindex):
sequ=[1]
index = 1
while index<mindex:
if (index == 1):
sequ.append(1)
else:
sequ.append(sequ[-1]+sequ[-2])
index += 1
return sequ
print(fibo_sequ(6))
'''使用递归,但是只能计算单个元素的值'''
'''函数体功能:使用递归计算单个索引下的斐波那契数列值'''
def fib(n):
if(n==1):
return 1
elif(n==2):
return 1
else:
return fib(n-1)+fib(n-2)
for i in range(1,7,1):
print(fib(i), end=' ')
[1, 1, 2, 3, 5, 8]
1 1 2 3 5 8
设置输出100个斐波那契数列的值,可以观察到使用递归的方法会非常的慢。并且从代码逻辑上说,显然使用递归会非常的浪费内存。
1. Mini计算器
说明:使用函数完成加、减、乘、除运算。
下面是代码示例:
def mini_cal(a, b, operation):
if operation=='+':
return a+b
elif operation=='-':
return a-b
elif operation=='*':
return a*b
elif operation=='/':
if b!=0:
return a/b
else:
return '除数不能为0'
else:
return '运算符输入错误'
if __name__ == '__main__':
a = int(input('请输入第一个整数:'))
b = int(input('请输入第一个整数:'))
oper = input('请输入运算符:')
print(mini_cal(a, b, oper))
请输入第一个整数:10
请输入第一个整数:0
请输入运算符:/
除数不能为0
2. 猜数游戏
说明:使用函数完成猜数游戏。
下面是代码示例:
import random
def guess(num_rand, num_guess):
if num_rand>num_guess:
return '猜小了'
elif num_rand<num_guess:
return '猜大了'
elif num_rand==num_guess:
return '猜对了'
if __name__=='__main__':
num_rand = random.randint(0,100)
for i in range(7,0,-1):
num_guess = int(input('请输入一个0~100的整数:'))
res = guess(num_rand,num_guess)
if res!='猜对了' and i>1:
print(res + f',你还有 {i-1} 次机会')
elif res!='猜对了' and i==1:
print(res + ',机会已经用完了~')
else:
print(res)
break
请输入一个0~100的整数:50
猜大了,你还有 6 次机会
请输入一个0~100的整数:25
猜小了,你还有 5 次机会
请输入一个0~100的整数:37
猜大了,你还有 4 次机会
请输入一个0~100的整数:31
猜小了,你还有 3 次机会
请输入一个0~100的整数:34
猜对了