python引用传递还是值传递,以及copy和deepcopy的区别

来源:互联网 发布:林书豪纪录片 知乎 编辑:程序博客网 时间:2024/05/18 12:36

原文地址http://www.cnblogs.com/loleina/p/5276918.html

原文地址http://www.cnblogs.com/buptldf/articles/4976561.html

传参时引用传递和值传递的区别

引用传递:被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
值传递:被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。

python中是如何传参的

python中的一切事物皆为对象,并且规定参数的传递都是对象的引用。可能这样说听起来比较难懂。参考下面一段引用:

  1. Python不允许程序员选择采用传值还是传 引用。Python参数传递采用的肯定是“传对象引用”的方式。实际上,这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典 或者列表)的引用,就能修改对象的原始值——相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能 直接修改原始对象——相当于通过“传值”来传递对象。
  2. 当人们复制列表或字典时,就复制了对象列表的引用同,如果改变引用的值,则修改了原始的参数。
  3. 为了简化内存管理,Python通过引用计数 机制实现自动垃圾回收功能,Python中的每个对象都有一个引用计数,用来计数该对象在不同场所分别被引用了多少次。每当引用一次Python对象,相 应的引用计数就增1,每当消毁一次Python对象,则相应的引用就减1,只有当引用计数为零时,才真正从内存中删除Python对象。

多说无益,直接上例子

def test(b):    print 'test before:%d,%d' % (b,id(b))    b = b + 10    print 'test after:%d,%d' % (b,id(b))if __name__ == '__main__':    a = 10    print 'main before:%d,%d' % (a,id(a))    test(a)    print 'main after:%d,%d' % (a,id(a))

运行结果

main before:10,35422448test before:10,35422448test after:20,35422208main after:10,35422448

图示
这里写图片描述

id函数可以获得对象的内存地址.很明显从上面例子可以看出,将a变量作为参数传递给了test函数,传递了a的一个引用,把a的地址传递过去了,所以在函数内获取的变量b的地址跟变量a的地址是一样的,但是在函数内,对b进行赋值运算,b的值从10变成了20,实际上10和20所占的内存空间都还是存在的,赋值运算后,b指向20所在的内存。而a仍然指向10所在的内存,所以后面打印a,其值还是10.

那python函数传参就是传引用?然后传参的值在被调函数内被修改也不影响主调函数的实参变量的值?

答案是否定的,正如上面所说的,当传递的参数是列表或者字典这种可变对象时,将直接对实参进行修改,让我们来看个例子

def test(b):    print 'test before:%d,%d' % (b[0],id(b[0]))    b[0] = b[0] + 10    print 'test after:%d,%d' % (b[0],id(b[0]))if __name__ == '__main__':    a = [10]    print 'main before:%d,%d' % (a[0],id(a[0]))    test(a)    print 'main after:%d,%d' % (a[0],id(a[0]))

运行结果

main before:10,35422448test before:10,35422448test after:20,35422208main after:20,35422208

图示
这里写图片描述

结论

Python参数传递采用的是“传对象引用”的方式。 这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象--相当于通过“传值’来传递对象。

copy 浅拷贝 只复制父对象,对象的内部的子对象依然是引用。

该怎么理解这句话,直接上例子

import copya = [1,2,3,[1,2,3]]b = copy.copy(a)a,bOut[4]: ([1, 2, 3, [1, 2, 3]], [1, 2, 3, [1, 2, 3]])a[3].append(4)a,bOut[8]: ([1, 2, 3, [1, 2, 3, 4]], [1, 2, 3, [1, 2, 3, 4]])a.append(5)a,bOut[11]: ([1, 2, 3, [1, 2, 3, 4], 5], [1, 2, 3, [1, 2, 3, 4]])id(a),id(b)Out[19]: (204294024L, 204380424L)id(a[3]),id(b[3])Out[18]: (204294856L, 204294856L)

b是a的浅拷贝对象,对a列表添加一个值时,b列表没有变化。当对a列表内的一个子列表添加一个值时,b列表也发生了同样的改变。由他们的地址也可以看出,a,b是存储在不同位置的两个列表,但是a[3]和b[3]指向的是同一个列表。由此可以得出, 浅拷贝时,拷贝的内部子对象是一个引用,不是真正意义上的拷贝。

deepcopy才是真正意义上的拷贝

a = [1,2,3,[1,2,3]]b = copy.deepcopy(a)a[3].append(4)a,bOut[15]: ([1, 2, 3, [1, 2, 3, 4]], [1, 2, 3, [1, 2, 3]])id(a[3]),id(b[3])Out[16]: (204293768L, 204294600L)