本篇文章主要是对python学习时的一些总结,作为学习笔记记录。
在上一篇文章中,主要说明了函数定义,函数参数和函数调用相关的内容,这里主要函数中的变量。
参数传递
上一篇文章中说了python函数的参数传递是值传递,传递的对象可以分为两类:
- 不可变对象:int、float、None、complex、bool、tuple、str、range
-
可变对象:dict、list
不可变对象为不可以在函数内部修改函数外部的变量,可变对象为可以在函数内部修改函数外部的变量。
但上述只是一种笼统的说法,这里我们使用具体的示例来观察不可变类型和可变类型的区别。
不可变对象传递
def func(var):
var = 10
print(var)
a = 5
print(a)
func(5)
print(a)
func(a)
print(a)
结果为:
5
10
5
10
5
从上边的例子我们可以看出:尽管在函数内部改变了a的大小,但函数外部的a的值没有变化。
考虑到python的特性,我们可以知道,传递不可变对象是让函数内部的实参指向了原有的不可变对象,而函数内部对该实参进行修改实际上是新生成了一个该不可变类型的对象,再让该实参指向新生成的不可变对象,因此也就不会改变函数外部的不可变对象的大小。
可变对象传递
def func(var):
var[1] = 10
print(var)
a = [1,2,3]
print(a)
func(a)
print(a)
结果为:
[1, 2, 3]
[1, 10, 3]
[1, 10, 3]
而如果传递的是可变对象,就能够改变该可变对象的值。这意味着对于可变对象来说,在函数内部对可变对象修改即是修改了外部可变对象,但实际上外部可变对象整体并没有发生变化,只是某一部分发生了变化。
对比C/C++来看,不可变类型的参数传递类似于C/C++中的值传递,传递的只是值,函数内部操纵的只是传入参数的副本。而不可变类型的参数传递类似于C/C++中的引用传递,函数内部操纵的是原始的传入参数,结果自然不同。
注意
1. 在传入参数时,可变参数(*)之前不能指定参数名
def func(a,*args):
print(a)
print(args)
func(10,20,30)
结果为:
10
(20, 30)
2. 函数传入实参时,可变参数(*)之后的参数必须指定参数名,否则就会被归到可变参数之中
def func(*args,a = None):
print(args)
print(a)
func(10,20,30)
func(10,20,a = 30)
结果为:
(10, 20, 30)
None
(10, 20)
30
3. 一个函数想要使用时必须明确指定参数名,可以将所有参数都放在可变参数之后
def func(a,*args,b = None):
print(a)
print(args)
print(b)
func(10,20,30)
func(10,20,b = 30)
结果为:
10
(20, 30)
None
10
(20,)
30
4. 关键字参数都只能作为最后一个参数,前面的参数按照位置赋值还是名称赋值都可以
def func(a,*args,b,**kwargs):
print(a)
print(args)
print(b)
print(kwargs)
func(10,20,b = 30)
func(10,20,b = 30,c = 40)
结果为:
10
(20,)
30
{}
10
(20,)
30
{'c': 40}
变量作用域
对于程序来说,程序内部的变量并不是在任何位置都能够访问到的,访问权限取决于该变量是在何处赋值的。
变量的作用域决定了在程序的哪一部分能够访问该变量,在python中,变量主要分为局部变量和全局变量。
局部变量
定义在函数内部的变量具有局部作用域,称为局部变量。局部变量只能够在其被声明的函数内部访问。
全局变量
声明在函数外层的变量具有全局作用域,称为全局变量。全局变量在所有的函数内都能够被访问。
- 对于函数来说,如果函数内部出现和全局变量同名的变量,那么通过变量名访问时,局部变量会覆盖全局变量
- 在函数内部可以直接读取全局变量
- 在函数内部如果要修改全局变量,该全局变量又是不可变类型,那么需要在函数内部重新使用global声明该全局变量
- 在函数内部如果要修改全局变量,该全局变量是可变类型,则不需要使用global声明
name = 'wood'
grade = [70,80,90]
def func1():
print(name, grade)
def func2():
name = "jack"
grade = [50,60,70]
print(name, grade)
def func3():
global name
name = 'tom'
grade.extend([10,20,30])
print(name, grade)
print(name,grade)
print('************')
func1()
print('************')
print(name,grade)
print('************')
func2()
print('************')
print(name,grade)
print('************')
func3()
print('************')
print(name,grade)
结果为:
wood [70, 80, 90]
************
wood [70, 80, 90]
************
wood [70, 80, 90]
************
jack [50, 60, 70]
************
wood [70, 80, 90]
************
tom [70, 80, 90, 10, 20, 30]
************
tom [70, 80, 90, 10, 20, 30]
上边的程序中,函数func3只是对全局变量grade进行修改,如果不是修改,而是重新赋值的话,就需要在函数内部用global声明该变量,否则会被认为是局部变量。
静态变量
在C/C++中,函数中可以定义静态变量作为该函数的特殊变量,该变量在函数调用结束后仍然保留,在下次函数调用仍旧保留上次的值。那么在python中应该怎么实现呢?
def func():
if not hasattr(func, 'num'):
func.num = 0
print(func.num)
func.num += 1
func()
func()
func()
结果为:
0
1
2
推测这种写法可以实现静态变量的效果是因为python中函数可能是一种特殊的类型,通过上述形式在函数func中添加了num属性,从而实现了静态变量的作用。
装包和解包
装包和解包是函数参数传递和函数返回时常用的概念,主要与*args,**kwargs和函数返回有关。
*args
def func(var, *args):
print(var)
print(type(args), ' ', args) # 未解包,args此时为tuple
print(*args) # 解包,等效于print(2,3,4)
func(1,2,3,4) # 装包
结果为:
1
<class 'tuple'> (2, 3, 4)
2 3 4
在参数传递时,参数被装包为tuple的形式,利用*可以对元组进行解包,解包相当于将元组外侧的()去除,直接暴露出tuple内部的数据。
**kwargs
def func(var, **kwargs):
print(var)
print(type(kwargs), ' ', kwargs) # 未拆包,args此时为dict
print(*kwargs) # 拆包,等效于print('var1','var2','var2')
func(1,var1 = 2,var2 = 3,var3 = 4) # 装包
结果为:
1
<class 'dict'> {'var1': 2, 'var2': 3, 'var3': 4}
var1 var2 var3
在参数传递时,参数被装包为dict的形式,利用*可以对dict进行解包,解包相当于将元组外侧的()去除,直接暴露出tuple内部的keys。
解包
def func(*args, **kwargs):
print(args)
print(kwargs)
a = (5,6,7)
b = {'var1': 2, 'var2': 3, 'var3': 4}
print(*a)
print(*b)
func(a,b)
func(*a,*b)
func(*a, **b)
从上面的代码可以看到:
- 解包可以在函数外部进行
- tuple可以原地解包,直接暴露内部的元素
- dict可以利用*解包,直接暴露内部的keys
- 参数传递时,如果tuple,dict未解包,则args,kwargs会对传入的参数再次装包
- 参数传递时,dict有两种解包形式,*解包为keys,**则解包为key-value对,也就是字典元素的形式,此时字典元素刚好可以交由kwargs装包
函数返回
python中函数可以返回多个变量,多个变量返回是将之装包为元组,然后return。
def func():
return 1,2,3
a = func()
print(a)
b,c,_ = func()
print(b)
print(c)
d,e,f = func()
print(d)
print(e)
print(f)
结果为:
(1, 2, 3)
1
2
1
2
3
从上边的结果可以看出:
- 可以使用函数返回值为变量赋值
- 如果等号左侧只有一个变量,则不解包,变量为返回值的元组形式
- 如果等号左侧有多个变量,则会解包,此时等号左侧的变量个数需要和解包之后的返回值个数对等
- 如果不需要某些变量,可以用 _ 表示,表示临时变量或与后续程序无关