Python中函数的参数传递问题,函数参数的传递往往是一个难以理解的概念,记得在C语言中有一个经典的例子如下所示:



此处)折叠或打开


1. int swap(int a,int b)
2. {
3. int temp;
4. = a;
5. = b;
6. = temp;
7.  
8. ;
9. }
10.  
11. int a = 10,b = 20;
12. ("Before Swap a = %d, b = %d\n",a,b);
13. (a,b);
14. ("After Swap a= %d,b = %d\n",a,b);

我想这是大部分学过C语言的人都会遇到的一段代码,printf过后两个变量的值并没有发生改变,这是为什么呢?必然知道这是因为C语言的参数是采用值传递的形式,存在形参与实参的区别,也就是将实参的值复制给形参,在函数内部操作的都只是形参的内容,并不改变实参的值,所以变量在操作过后并没有发生改变。通常采用的方法是传递指针的形式,为什么传递指针又可以解决问题呢?这是因为传递的指针是指上就是一个地址值,也就是说形参和实参都指向了一段内存区域,在函数内部对内存区域的内容进行改变,这样就会影响到实参指向的内存区域,这样就实现了内存中数据的修改,进而实现数据的交换操作,这也是C语言中指针的经典操作之一。


 


但是到Python以后,我发现与C语言存在较大的差别,Python中万物皆对象,没有指针等特性使得我有些难以理解。Python万物皆对象的特征让我逐渐有了一定的理解。首先说明一下万物皆对象的问题。


此处)折叠或打开

1. >>> IntNum = 10
2. >>> Num1 = IntNum
3. >>> id(IntNum),id(Num1)
4. (10417212, 10417212)
5. >>> Num2 = 10
6. >>> id(IntNum),id(Num1),id(Num2)
7. (10417212, 10417212, 10417212)
8. >>> intNum = 20
9. >>> Num1 = 30
10. >>> Num2 = 40
11. >>> id(IntNum),id(Num1),id(Num2)
12. (10417212, 10416972, 10416852)


这段代码主要是同id(object)判断了变量的ID号,这个ID号实质上也就表明了变量指向的对象(我这样认为的)。


IntNum = 10,是指IntNum这个变量实质上是指向了一个int类型的对象10,同时Num1 = IntNum则表示Num1这个变量也指向10这个对象。同样Num2 = 10也表明了指向10这个int对象,可以通过id()判断。具体的实现原理是采用了一种叫做引用计数的技术完成的,这是解释器的实现,与使用者关系并不大。因此可以将左值看做变量,就如同10是对象,而IntNum就是对象的引用,是一个变量。变量赋值给变量相当于同一对象引用计数器增加1,而并不重新分配对象。


 


同样IntNum = 20,则是指重新分配一个对象20,让IntNum指向这个对象,这时10这个对象的引用计数器要减1,因为IntNum不在引用10这个对象啦。


 


前面单一元素的对象还比较容易理解,但是下面这个的对象就不一定能够理解啦。


此处)折叠或打开

1. #list
2. >>> list1 = [1,2,3,4,5]
3. >>> list2 = [1,2,3,4,6]
4. >>> id(list1),id(list2)
5. (19050128, 19034520)
6. >>> list1[4]=6
7. >>> list1
8. [1, 2, 3, 4, 6]
9. >>> id(list1),id(list2)
10. (19050128, 19034520)
11.  
12.  #dict
13. >>> dict1 = {'a':1,'b':2,'c':3,'d':4}
14. >>> dict2 = {'a':1,'b':2,'c':3,'d':5}
15. >>> id(dict1),id(dict2)
16. (19042496, 19021232)
17. >>> dict1['d'] = 5
18. >>> dict1
19. {'a': 1, 'c': 3, 'b': 2, 'd': 5}
20. >>> dict2
21. {'a': 1, 'c': 3, 'b': 2, 'd': 5}
22. >>> id(dict1),id(dict2)
23. (19042496, 19021232)

从结果可以看见,对于列表而言,当改变了列表中某一个局部对象后,列表的地址并没有改变,这样对象的id也就不能改变了。说明列表局部内容是可以修改的,但是列表对象的ID号(存储地址)不会发生改变。同样对于字典类型的数据也可以知道,让dict1、dict2分别指向两个字典对象,这两个字典对象的id号存在差别,当修改其中一个的内容使两个字典的内容一样,这时候判断ID,仍然是不同的,说明字典的也是可以修改的。


比如dict1['d']=5是指,原来‘d’指向的对象是4,这时候重新分配一个对象5,让‘d’指向这个对象5。这时候并不改变字典变量dict1的值(已分配字典对象的地址)。


 


综合上述,Python中的变量是一个对象的引用,变量于变量之间的赋值是对同一个对象的引用,当变量重新赋值对象时,指将这个变量指向一个新分配的对象。这是和C语言中的变量存在差别。但是Python中的变量有点类似C语言中的指针,指向的是一个对象,或者一段内存空间,这段内存空间的内容是可以修改的(这也是为什么对列表或者字典的某一个子对象进行修改并不改变字典或者列表的ID号),但是内存的起始地址是不能改变的,指针变量之间的赋值相当于两个指针变量指向同一块内存区域,在Python中就相当于同一个对象。因此可以认为Python中的变量相当于C语言中的指针变量。


 


接下来分析函数中的参数传递问题:由于在Python中函数的参数传递是值传递,同时也存在局部和全局的问题,这和C语言中的函数也存在一定的相似性。


函数的定义形式如下:


此处)折叠或打开

1. def function(args):
2.      function_block


参数传递过程中存在两个规则:


1、通过引用将参数复制到局部作用域的对象中,意味着被用来访问函数参数的变量于提高给函数的对象无关,因为存在一个复制问题,这和C语言是相同的。而且修改局部对象不会改变原始数据。


2、可以在适当位置修改可变对象。可变对象主要就是列表和字典,这个适当位置实质上就是前面分析的局部子对象的修改不会改变字典对象或者列表对象的ID,也就是存储位置(这是我暂且这么称呼吧)。


通过两个实例说明,第一个还是交换问题:


此处)折叠或打开

1. >>> def modifier(number,string,list):
2. = 5
3. = 'GoodBye'
4. = [4,5,6]
5. print "Inside:", number,string,list
6.  
7.      
8. >>> num = 10
9. >>> string = 'Hello'
10. >>> list = [1,2,3]
11. >>> print 'Before:', num, string, list
12. : 10 Hello [1, 2, 3]
13. >>> modifier(num,string,list)
14. : 5 GoodBye [4, 5, 6]
15. >>> print 'After:', num, string, list
16. : 10 Hello [1, 2, 3]


从上面的结果可以看出来数据交换前后数据并没有发生改变,虽然在函数局部区域对传递进来的参数进行了相应的修改,但是仍然不能改变实参对象的内容。这和C语言中的操作非常相似,因为传递进来的三个指针在函数内部进行了相关的修改,相当于三个指针分别指向了不同的对象(存储区域),但是这三个指针都是局部指针并不改变实际的指针,所以交换前后实参指向的对象并没有发生改变。说明如果在函数内部对参数重新赋值新的对象,这并不会改变实参的对象。这就是函数的第一个规则。


对于不可变的对象,是不可能进行修改的,但是对于可变的对象(字典、列表),局部区域的值倒是可以改变的,这和前面分析的一样,看以参看下面的例子。


此处)折叠或打开


1. >>> def modifier(list,dict):
2. [0] = 10
3. ['a'] = 10
4. print 'Inside list = %s, dict = %s' %(list,dict)
5.  
6. >>> dict = {'a':1,'b':2,'c':3}
7. >>> list = [1,2,3,4,5]
8. >>> print 'Before list = %s, dict = %s' %(list,dict)
9. = [1, 2, 3, 4, 5], dict = {'a': 1, 'c': 3, 'b': 2}
10. >>> modifier(list,dict)
11. = [10, 2, 3, 4, 5], dict = {'a': 10, 'c': 3, 'b': 2}
12. >>> print 'After list = %s, dict = %s' %(list,dict)
13. = [10, 2, 3, 4, 5], dict = {'a': 10, 'c': 3, 'b': 2}


从上面的结果可以看出来,在函数内部修改列表、字典的局部对象或者说没有对传递进来的列表、字典变量重新赋值对象,而是修改变量的局部内容,这时候就会导致外部实参指向对象内容的修改,这就相当于在C语言中对指针指向的内存区域进行修改,这样的修改必然会导致实参指向区域内容的改变。这是函数规则的第二条,适当的位置指的是对对象进行修改,而不是重现分配一个对象,重现分配一个对象不会影响实参,而对对象的修改必然影响实参。


 


在C语言中返回多对象时必然会引入指针的操作,因为对指针的修改实质上会反映到实参,这样就实现了数据的返回操作。而在Python中采用元组的形式返回多个值。但是知道了函数参数的传递特性,我们完全可以采用函数的参数实现一些基本的操作,就比如刚开始讨论的交换问题,如下所示:

此处)折叠或打开


1. >>> def swap(list):
2. = list[0]
3. [0] = list[1]
4. [1] = temp
5.  
6.      
7. >>> list = [10,20]
8. >>> list
9. [10, 20]
10. >>> swap(list)
11. >>> list
12. [20, 10]


从上面的实验结果可知实现了数据的交换问题,这仅仅是为了说明利用参数计算的一些可能性,在实际中怎么运用我也是新手。