在讲函数参数之前还是简单的讲一下Python中的可变对象与不可变对象。
一、可变对象与不可变对象
在Python中,一切皆对象,python中不存在所谓的传值调用,一切传递的都是对象的引用,也可以认为是传址。所谓可变对象是指,对象的内容可变,而不可变对象是指对象内容不可变(即在其创建后,值不能改变,但可创建新的对象并以同一变量名对其赋值,而旧的对象会被清理掉,这在python里叫对象的垃圾收集)。
不可变(immutable):int、字符串(string)、float、(数值型number)、元组(tuple)
可变(mutable):字典型(dictionary)、列表型(list)
看下面例子:i = 73
i += 2
从上图可知,不可变对象的特征没有变,变的只是创建了新对象,改变了变量的对象引用。a = 1
b = 1
print a is b
输出的结果是True,这说明a和b指向同一对象,这在python里叫共享引用。在python中变量总是指向对象的指针,而不是可改变的内存区域的标签:给一个变量赋一个新值,并不是替换了原始对象(原始对象依旧在,只是没有变量去引用,最后被垃圾收集机制给回收了),而是让这个变量去引用完全不同的对象。
可变对象例子:m = [5,9]
m += [6]
其对象的内容是可以变化的。当对象的内容发生变化时,变量的对象引用(也就是内存地址)是不会变化的
二、函数的参数
对于函数传递参数时要注意:
(1)参数传递是通过自动将对象赋值给本地变量来实现的。函数参数在实际应用中只是python赋值的另一个实例而已。因为应用是以指针的形式实现的,所有的参数实际上都是通过指针进行传递的。
(2)在函数内部的参数名赋值不会影响调用者。
(3)改变函数的可变对象参数的值也许会对调用者有影响
第三条要仔细理解下,如下面例子def changer(a,b):
a = 2
b[0] = "spam"
x = 1
L = [1,2]
changer(x,L)
print x,L
输出的结果并不是1,[1,2] 而是1,['spam',2]
如果对Python的作用域有了解的话,你会觉得x,L是全局变量,a,b是本地变量,调用后对全局变量无影响呀,但是要注意:L是列表,它是可变对象,它的值被赋予给b后,函数里第二个赋值语句是在原处改变这个对象,对b[0]进行赋值的结果会在函数返回后影响L的值。实际上并没有修改b,而是修改b当前所引用的对象的一部分,并且这个改变会影响调用者。
由此可知:
. 不可变参数是“通过值”进行传递。像整数等不可变对象是通过对象引用而不是拷贝进行传递的,但是因为无论怎样都不可能在原处修改不可变对象,实际的效果就是很像创建了一份拷贝,浅拷贝
. 可变对象是通过“指针”进行传递。
所以要尽量避免可变对象参数的修改,可以用深拷贝来避免这个问题,也就是说将可变对象深拷贝一份传递到函数中,然后让函数调用拷贝的对象。如下:
方法一:def changer(a,b):
a = 2
b[0] = "spam"
x = 1
L = [1,2]
changer(x,L[:])
print x,L
方法二:def changer(a,b):
b = b[:]
a = 2
b[0] = "spam"
x = 1
L = [1,2]
changer(x,L)
print x,L
如果不了解深拷贝和浅拷贝的话,自行谷歌一下,在此不做介绍,望谅解!
三、参数匹配模型
匹配模型主要有以下几个:
(1)位置:从左到右进行匹配
(2)关键字参数:通过参数名进行匹配
(3)默认参数:为没有传入值的参数定义参数值
(4)可变参数:收集任意多基于位置或者关键字的参数
(5)可变参数:传递任意多的基于位置或关键字的参数
(一)、关键字参数和默认参数实例def f(a,b,c):
print a,b,c
在没有特殊情况下,该函数默认情况下就是按位置传递值,比如f(1,2,3),打印出来的就是1,2,3
但是在Python中,允许通过关键字参数通过变量名来进行匹配,而不是通过位置。>>>f(c=3,b=2,a=1)
打印结果也是1,2,3
Python将调用中的变量名c匹配给在函数定义头部名为c的参数,并将值3传递给哪个参数,a,b也是一样。关键字参数使用时参数从左到右的位置不在重要了。在一些调用中,混合使用位置的参数和基于关键字的参数,这种情况,所有基于位置的参数首先按照从左到右的顺序匹配头部参数,之后再进行基于关键字进行参数匹配
默认参数允许创建函数可选参数,如果没有传入值的话,在函数运行前,参数被赋了默认值。def f(a,b=2,c=3):
print a,b,c
f(1),f(1,4)这些引用都合法。
默认参数必须位于函数定义头部的右端,下面这种定义方式就不对了。def f(a,b=2,c,d=4):
print a,b,c,d
这种定义方法是错误的
(二)、任意参数
*和**是支持让函数接受任意数目参数的,它们都可以出现在函数的定义或者调用中。
定义和调用就是收集参数和分解参数的过程
(1)收集参数
*和**是在函数定义中收集参数,前者在元组中收集不匹配的位置参数;**特性类似,但是它只对关键字参数有效,将这些关键字参数传递给新的字典。>>>def f(*args):print(args)
>>>f()
()
>>>f(1)
(1,)
>>>f(1, 2, 3, 4)
(1, 2, 3, 4)>>>def f(**args): print((args)
>>>f()
{}
>>>f(a = 1, b = 2)
{'a': 1, 'b': 2}
以后可以用这种方法创建字典了。>>>def f(a, *pargs, **kargs): print(a, pargs, kargs) ...
>>>f(1, 2, 3, x = 1, y = 2)
1 (2, 3) {'y': 2, 'x': 1}
可以看到,这两个是python中的可变参数。*args表示任何多个无名参数,它是一个tuple;**kwargs表示关键字参数,它是一个dict。并且同时使用*args和**kwargs时,必须*args参数列要在**kwargs前,像foo(a=1,
b='2', c=3, a', 1, None, )这样调用的话,会提示语法错误“SyntaxError: non-keyword
arg after keyword arg”。
(2)分解参数
在函数调用中,*和**就是分解参数。*会分解参数的集合,而不是创建参数的集合。相似的,在函数调用中,**会以键/值对的形式分解一个字典,使其成为独立的关键字参数>>>def func(a, b, c, d): print(a, b, c, d)
>>>args = (1, 2)
>>>args += (3, 4)
>>>func(*args)
1 2 3 4>>>args = {'a': 1, 'b': 2, 'c': 3}
>>>args['d'] = 4
>>>func(**args)
1 2 3 4