python-内存管理2:拷贝,参数传递

结合python 内存管理更好理解

Python的一个容器对象(container),比如表、词典等,可以包含多个对象。实际上,容器对象中包含的并不是元素对象本身,是指向各个元素对象的引用。

1
2
3
x = [1, 2, 3]
y = [x, dict(key1=x)]
z = [y, (x, y)]

变量都是引用, 变量没有类型, 引用没有类型, 只有对象有类型

对z[0]的修改会修改z的对象的值, z[0]=y, 修改z[0]=x, 会修改了z的值, 但是不会修改y的取值,;z[0][1]=2, 会修改y的取值,z的值, 但是不会修改x的值. 诸如此类.

修改引用的某个子对象的取值,不会修改原先子对象, 只会新建一个子对象 . 但是会修改引用对象的值

1
2
3
4
5
nfoo = 1
nfoo = 2
lstFoo = [1]
lstFoo[0] = 2

代码第2行中,内存中原始的1对象因为不能改变,于是被“抛弃”,另nfoo指向一个新的int对象,其值为2

代码第5行中,更改list中第一个元素的值,因为list是可改变的,所以,第一个元素变更为2。其实应该说,lstFoo指向一个包含一个对象的数组。赋值所发生的事情,是有一个新int对象被指定给lstFoo所指向的数组对象的第一个元素,但是对于lstFoo本身来说,所指向的数组对象并没有变化,只是数组对象的内容发生变化了。这个看似void*的变量所指向的对象仍旧是刚刚的那个有一个int对象的list。
enter image description here

拷贝

对象的赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用。

当你对一个对象赋值的时候(做为参数传递,或者做为返回值),Python和Java一样,总是传递原始对象的引用,而不是一个副本。
如果你想修改一个对象,而且想让原始的对象不受影响,那你就需要对象复制。

浅拷贝

可以 使用copy.copy(),它可以进行对象的浅复制(shallow copy),它复制了对象,但对于对象中的元素,依然使用引用.

以下都是浅拷贝:

(1)、使用切片[:]操作进行拷贝

(2)、使用工厂函数(如list/dir/set)等进行拷贝

(3)、copy.copy()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [1]: a={}
In [2]: a['a']=111
In [3]: c=[a,1,2,3,a]
In [4]: d=list(c)
In [5]: d[0]['a']=222
In [6]: c
Out[6]: [{'a': 222}, 1, 2, 3, {'a': 222}]
In [7]: d[0]=1
In [8]: c
Out[8]: [{'a': 222}, 1, 2, 3, {'a': 222}]

另外: python中字符串不可以修改,所以在为tom和anny重新命名的时候,会重新创建一个’tom’和’anny’对象,替换旧的’jack’对象。

浅复制(shallow copy),它复制了对象,但对于对象中的元素,依然使用引用.

深拷贝

如果希望复制一个容器对象,以及它里面的所有元素(包含元素的子元素),使用copy.deepcopy,这个方法会消耗一些时间和空间,不过,如果你需要完全复制,这是唯一的方法.

1
2
3
4
5
6
7
8
9
In [11]: d=copy.deepcopy(c)
In [12]: c
Out[12]: [{'a': 222}, 1, 2, 3, {'a': 222}]
In [13]: d[0]['a']=000
In [14]: c
Out[14]: [{'a': 222}, 1, 2, 3, {'a': 222}]

注意:

1、对于非容器类型(如数字、字符串、和其他‘原子’类型的对象)没有被拷贝一说。

2、如果元祖变量只包含原子类型对象,则不能深copy。

参数传递

和其他语言不一样,传递参数的时候,python不允许程序员选择采用传值还是传引用。Python参数传递采用的肯定是“传对象引用”的方式。实际上,这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象--相当于通过“传值’来传递对象。