在讲函数参数之前还是简单的讲一下Python中的可变对象与不可变对象。

一、可变对象与不可变对象

     在Python中,一切皆对象,python中不存在所谓的传值调用,一切传递的都是对象的引用,也可以认为是传址。所谓可变对象是指,对象的内容可变,而不可变对象是指对象内容不可变(即在其创建后,值不能改变,但可创建新的对象并以同一变量名对其赋值,而旧的对象会被清理掉,这在python里叫对象的垃圾收集)。
不可变(immutable):int、字符串(string)、float、(数值型number)、元组(tuple)
可变(mutable):字典型(dictionary)、列表型(list)

看下面例子:

i = 73i += 2

从上图可知,不可变对象的特征没有变,变的只是创建了新对象,改变了变量的对象引用。

a = 1b = 1print 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 = 1L = [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 = 1L = [1,2]changer(x,L[:])print x,L

方法二:

def changer(a,b):    b = b[:]    a = 2    b[0] = "spam"    x = 1L = [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