python 参数传递方式也是老生常谈的问题,这里做个简单总结
语言的特性决定了参数的传递方式,先回顾一下,什么是传值,什么是传引用:
传值(pass by value):被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。 值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
传引用(pass by reference):被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。 被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
前面的文章里,我们多次提到:python中一切皆对象。
所以这里,变量也是对象。我们常说,python是动态语言,随之,python是动态类型的强类型语言。
动态语言的特点是它可以在运行时更改自己的结构,例如添加、删除或替换函数,以及对象/类的属性和方法等。
class C:
pass
c = C()
c.val = 1
强类型指的是一个变量被赋值为一个对象后,这个对象的类型就固定了,不能隐式转换成另一个类型。
a = 1 #这里的1是int类型,不能转变为其他类型
动态类型是指一个变量可以替换成不同类型的对象。
a = 1
a = "I change my type" # 这里,只是a指向的对象变了,而上面的1的地址依然是1
所以,我们姑且这样说: 变量是没有类型的,值(对象)才有类型。这很重要,这是理解下面类容的基本知识
intval = 1 # 一个指向int类型的变量intval
listval = [1] # 一个指向list类型的变量listval
熟悉python的同学都知道,python分为可变和不可变对象。 我们要知道的是所谓可变对象是指,对象的内容可变,而不可变对象是指对象内容不可变 python中对他们的简单分类如下:
>>> intval = 1
>>> id(intval)
140229478498760
>>> intval = 2
>>> id(intval)
140229478498736
从上面可以看出,对于不可变对象,改变变量值,只是变量指向的地址发生了变化,而原来地址中的对象并没有发生变化。
>>> listval=[1, 2, 3]
>>> id(listval)
4373130920
>>> id(listval[0])
140229478498760
>>> listval[0] = 11
>>> id(listval)
4373130920
>>> id(listval[0])
140229478498520
>>>
从上面可以看出,当改变listval中元素值的时候,listval本身的地址并没有发生变化,也就是说,listval是可变的。有趣的是,这里listval[0]的地址却发生了变化,这恰好符合了python是强类型语言的定义,对象本身不变,而是变量指向的地址改变了。
先说结论:对于不可变对象作为参数,看起来就好像c++中的传值;对于可变对象作参数,看起来就好像是C++中的传引用。下面举例说明
def argument_int(a):
print id(a)
a = 10
print id(a)
intval = 2
print id(intval)
argument_int(intval)
print intval #最后的值依然是2
这里发生了什么?首先我们初始化了一个intval变量的值为2,然后传递给函数,这个时候,函数参数a的地址和intval的地址还是一样的。然后,我们改变了a指向的对象,然而,此时的intval指向的对象依然没有发生变化。整个过程看起来就跟C++中的传值调用一样。
def argument_list(a):
a[0] = 1000
listval = [1]
argument_list(listval)
print listval # 输出[1000]
就跟我们上一节说的一样,此时函数的参数a和listval指向的地址是一样的。函数内部改变a元素指向的对象,相应的listval值也会发生变化,看起来就好像是传引用调用一样。
对于其他对象,只要我们能定义成可变对象(如dict,class等),它的调用过程就是传引用的,反之亦然。