函数进阶
- 变量作用域
- 多函数程序执行流程
- 函数的返回值
- 函数的参数
- 拆包和交换两个变量的值
- 引用
- 可变和不可变类型
1 变量作用域
变量作用域指的是变量生效的范围,主要分为两类:局部变量和全局变量
1.1 局部变量
所谓局部变量是定义在函数体内部的变量,即只在函数体内部生效。
如下代码:
def testA():
a = 100
# 函数体内部访问变量 a
print(a)
testA() # 输出 100
# 函数体外部访问 a 变量
print(a) # NameError: name 'a' is not defined ,因为 a 的作用域在 testA() 函数中
变量a是定义在 testA 函数内部的变量,在函数外部访问则立即报错
局部变量的作用: 在函数体内部,临时保存数据,即当函数调用完成后,则销毁局部变量。
1.2 全局变量
所谓全局变量,指的是在函数体内、外都能生效的变量。
思考: 如果有一个数据,在函数 A 和函数 B 中都要使用,该怎么办?
答: 将这个数据存储在一个全局变量里面
# 定义全局变量,并赋值 100
a = 100
# 定义 testA()
def testA():
# 打印 a
print(a)
# 定义 testB()
def testB():
# 打印 a
print(a)
# 调用 A B 函数
testA()
testB()
# 外部调用 a 变量
print(a)
[10:21:45 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
100
100
100
思考: testB 函数需求修改变量a的值为200,如何修改程序?如下代码:
a = 100
def testA():
print(a)
def testB():
# testB 函数中的变量 a ,这里是重新声明了一个局部变量 a 而非父作用域的变量 a
a = 200
print(a)
testA()
testB()
print(a)
思考: 在 testB 函数内部的 a=200
中的变量 a 是在修改全局变量 a 吗?
答: 不会。因为父作用域不会继承子作用域的变量
1.3 修改全局变量
如果我们想在函数中修改全局变量的值,那么就需要使用到 global
关键字,如下代码:
a = 100
def testA():
print(a)
def testB():
global a # 通过 global 关键字,声明 a 为全局变量
a = 200
print(a)
testA()
testB() # 200 ,因为已经修改了 a 变量
print(a) # 200 全局变量中 a 已经修改
[10:30:48 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
100 # 因为先调用 testA 函数,但是那会 testA 中的 a 变量并没有修改所以还是100
200
200
总结:
-
如果在函数里面直接把变量 a=200 赋值,此时的 a 不是全局变量的修改,而是相当于在函数内部声明了一个新的局部变量
-
函数体内部修改全局变量步骤如下:
a.通过
global
声明全局变量b.在重新调用声明过的变量即可
2 多函数程序执行流程
一般在实际开发过程中,一个程序往往由多个函数 (后面知识中会讲解类)组成,并且多个函数共享某些数据,如下所示:
如果多个函数要同时使用一个共同数据,那么该数据的生效范围就是一个全局的,因为局部变量只能在某一个函数内部生效,从而无法实现多个函数共享某个共同数据
所以我们所谓的多函数程序执行流程也就是,如果多个函数共享一个全局变量,那么这个时候全集变量随着函数的执行而改变
- 共用全局变量
# 1.定义全局变量
glo_num = 0
def test1():
# 定义 glo_num 为全局变量
global glo_num
# 修改 glo_num 为 100
glo_num = 100
def test2():
# 调用 test1 函数中修改后的全局变量
print(glo_num)
# 因为没有执行 test1 函数,所以并没有修改 glo_num 的值,依旧输出 0
print(glo_num)
# 2.调用 test1 函数,执行函数内部代码:声明和修改全局变量
test1()
# 3.调用 test2 函数,执行函数内部代码
test2()
print(glo_num)
[14:21:17 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
0
100
100
3 函数返回值
3.1 返回值作为参数传递
'''
1.定义两个函数
2.函数 1 返回 50
3.函数 2 调用函数 1 并接收返回值 50 进行输出
'''
# test1 函数返回值 50
def test1():
return 50
# test2 形参为 num
def test2(num):
# 输出 num 形参
print(num)
# num1 变量等于 test1() 返回值也就是 50
num1 = test1()
# 方法一:
# 传入 num1 变量输出 50
test2(num1)
# 方法二:
# 直接调用 test1() 返回值
test2(test1())
[14:28:05 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
50
50
3.2 函数的返回值详解
思考: 如果一个函数如下两个 return (如下所示),程序如何执行?
def return_num():
return 1
return 2
result = return_num()
# 这里只能输出 1,因为我们都知道在函数中如果遇到 return 那么就不在执行下面代码而退出函数
print(result)
答: 只执行了第一个return,原因是因为 return 可以退出当前函数,导致 return 下方的代码不执行。
[14:29:29 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
1
但是如果我们工作中需要一个函数返回多个值就得看下面的代码:
def return_num():
# 直接在 return 后面跟上 1,2
return 1,2 # 返回元组
# num1,num2 接收 return_num() 的返回值
num1 , num2 = return_num()
# 方法一:输出 num1,num2
print(num1,num2)
# 方法二:直接调用函数输出
print(return_num())
[14:34:30 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
1 2
(1, 2)
注意:
return a,b
写法,返回多个数据的时候,默认是元组类型- return 后面可以链接列表、元组或字典,已返回多个值
3.3 return 返回值类型
3.3.1 return 返回元组
def return_num():
return 10,20
# result 接收 return_num 函数返回值,默认为元组
result = return_num()
print(result)
print(type(result))
# 可以看到输出的是一个元组
[15:13:42 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
(10, 20)
<class 'tuple'>
3.3.2 return 返回列表
def return_num():
# 返沪时候添加 [] 就是列表
return [10,20]
# result 接收 return_num 函数返回值
result = return_num()
print(result)
print(type(result))
[15:15:36 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
[10, 20]
<class 'list'>
3.3.3 return 返回字典
def return_num():
return {"name":"zgy","age":12}
# result 接收 return_num 函数返回值
result = return_num()
print(result)
print(type(result))
[15:48:41 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
{'name': 'zgy', 'age': 12}
<class 'dict'>
3.3.4 return 返回集合
def return_num():
return {10,20}
# result 接收 return_num 函数返回值
result = return_num()
print(result)
print(type(result))
[15:48:42 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
{10, 20}
<class 'set'>
4 函数的参数
4.1 位置参数
位置参数: 调用函数时根据函数定义的参数位置来传递参数
def user_info(name,age,gender):
print(f'名字:{name},年龄:{age},性别:{gender}')
user_info('tom',18,'男')
[15:49:42 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
名字:tom,年龄:18,性别:男
注意:
传递和定义参数的顺序及个数必须一致
测试:个数问题
# 这里我将 男 这个形参删掉,只传入两个形参观察报错
def user_info(name,age,gender):
print(f'名字:{name},年龄:{age},性别:{gender}')
user_info('tom',18)
[16:41:45 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
Traceback (most recent call last):
File "/root/py-demo/day-1/test.py", line 4, in <module>
user_info('tom',18)
TypeError: user_info() missing 1 required positional argument: 'gender'
# 提示缺少 gender 参数
注意:
所以定义的形参和传入的参数个数不一样就会发生报错
测试:顺序问题
def user_info(name,age,gender):
print(f'名字:{name},年龄:{age},性别:{gender}')
user_info("男",'tom',18)
[16:42:17 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
名字:男,年龄:tom,性别:18
注意:
虽然没有报错,但是我们拿到的数据也没有任何意义,因为我们定义的数据顺序也必须和我们的传入顺序保持一致,否则导致数据无意义
4.2 关键字参数
函数调用,通过"键=值"
形式加以指定。可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求
因为在上面 4.1 位置参数中,我们知道有一个注意事项就是传入真实数据的位置顺序和定义形参的顺序必须是一样的,如果不一样虽然程序不会报错,但是会导致用户得到的数据是毫无意义的
所以现在我们就需要学习关键字参数,因为在关键字参数中都是"键=值"
的形式进行指定传入数据,这就解决了位置参数必须按照顺序来书写的这么一个需求,从而让我们每个函数的参数顺序作用更加清晰
如下代码:
def user_info(name,age,gender):
print(f'名字:{name},年龄:{age},性别:{gender}')
# 这里指定 age=19,gender="男"
user_info("tom",age=19,gender="男")
# 关键字参数可以实现打乱顺序传递
user_info(age=20,name="haha",gender="女")
[17:05:05 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
名字:tom,年龄:19,性别:男
名字:haha,年龄:20,性别:女
总结:
通过关键字参数的方式就可以实现不在注意传参顺序,变得更加方便和边界
注意:
函数调用时,如果有位置参数时,位置参数必须在关键字参数的前面,但关键字参数之间不存在先后顺序。
4.3 缺省参数
缺省参数也叫默认参数,用于定义函数,为参数提供默认值,调用函数时可不传该默认参数的值 (注意: 所有位置参数必须出现在默认参数前,包括函数定义和调用)
所谓的默认参数无非就是在我们定义函数的时候要定义相关的形参,那么这个时候我们可以把某个形参或某些形参设置为我们的默认值,从而简化程序节省用户的操作成本
需求:
如下代码我们想实现的是男性用户在注册的时候性格无需手动填写,而是自定填写,因为该产品大部分的用户都是男性从而满足节省用户操作成本和时间成本
# gender 默认值为 男
def user_info(name,age,gender='男'):
print(f'名字:{name},年龄:{age},性别:{gender}')
# 通过缺省参数实现,并没有传入 gender 的值
user_info('tang',12)
# 手动传递女 gender 值为女
user_info('mali',30,'女')
[17:05:56 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
名字:tang,年龄:12,性别:男 # 默认输出 男
名字:mali,年龄:30,性别:女 # 手动输出 gender=女
注意:
函数调用时,如果为缺省参数传值则修改默认参数值;否则使用这个默认值。
4.4 不定长参数
不定长参数也叫可变参数。用于不确定调用的时候会传递多少个参数(不传参也可以)的场景。此时,可用包裹(packing)位置参数,或者包裹关键字参数,来进行参数传递,会显得非常方便。
比如我们在注册邮箱时,有些注册条件是带*
号必填项,而有些却不带,也就是说但凡是必填项的数据用户是需要填入的,但是还有很多不是必填项的数据是可传入或不可传入也是可以的
所以通过上面的需求可以知道用户传入的数据个数不一定,从而引出不定长参数来实现需求
而对于不定长参数我们又分为两大类:
- 包裹位置传递:接收所有不定位置参数且不确定个数的时候来使用
- :为了接收关键字参数且不确定个数的时候使用
4.4.1 包裹位置传递
# * 就是关键点,*args 该形参就是接收所有不定长位置参数
# * 是必须写的,args 可以通过不同变量名实现
def user_info(*args):
print(args)
user_info('tom')
user_info('sanli',20)
user_info()
[17:29:34 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
('tom',)
('sanli', 20)
()
注意:
传进的所有参数都会被 args 变量收集,它会根据传进参数的位置合并为一个元组(tuple),args 是元组类型,这就是包裹位置传递。
4.4.2 包裹关键字传递
# ** 就是关键点,**kwargs 该形参就是接收所有不定长位置参数
# ** 是必须写的,kwargs 可以通过不同变量名实现
def user_info(**kwargs):
print(kwargs)
user_info(name='tom',age=18,id=1)
[17:38:48 root@dev py-demo]#/bin/python3 /root/py-demo/day-1/test.py
{'name': 'tom', 'age': 18, 'id': 1}
综上: 无论是包裹位置传递还是包裹关键字传递,都是一个组包的过程。
所以这种搜集所有零散数据并返回一个整体,这个过程我们就叫做组包的过程