Python中的赋值和拷贝

参考链接: http://www.cnblogs.com/wilber2013/p/4645353.html

Python中的对象赋值,浅拷贝,深拷贝是不同的三个方法,下面谈下他们之间的差异。


对象赋值

先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
>>> a = ['great', 4, ['go','ok','C#']]
>>> a
['great', 4, ['go', 'ok', 'C#']]
>>> b = a
>>> b
['great', 4, ['go', 'ok', 'C#']]
>>> [id(x) for x in a]
[139697967318608, 10914464, 139697936699848]
>>> [id(x) for x in b]
[139697967318608, 10914464, 139697936699848]
>>> a[0] = 'change'
>>> a
['change', 4, ['go', 'ok', 'C#']]
>>> b
['change', 4, ['go', 'ok', 'C#']]
>>> [id(x) for x in a]
[139697936776808, 10914464, 139697936699848]
>>> [id(x) for x in b]
[139697936776808, 10914464, 139697936699848]
>>> a[1] = 3
>>> a
['change', 3, ['go', 'ok', 'C#']]
>>> b
['change', 3, ['go', 'ok', 'C#']]
>>> [id(x) for x in a]
[139697936776808, 10914432, 139697936699848]
>>> [id(x) for x in b]
[139697936776808, 10914432, 139697936699848]
>>> a[2].append('yes')
>>> a
['change', 3, ['go', 'ok', 'C#', 'yes']]
>>> b
['change', 3, ['go', 'ok', 'C#', 'yes']]
>>> [id(x) for x in a]
[139697936776808, 10914432, 139697936699848]
>>> [id(x) for x in b]
[139697936776808, 10914432, 139697936699848]
>>> a.append((1,2))
>>> a
['change', 3, ['go', 'ok', 'C#', 'yes'], (1, 2)]
>>> a[3].append(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'append'

分析一下:

  1. 首先创建了一个名为a的变量,这个对象指向一个list对象,我们使用列表生成式可以看到a中所有元素对象的地址。
  2. 然后通过a变量对b变量进行赋值,那么b将指向a对应的对象(内存地址),也就是说“b就是a,b[i]就是a[i]”,这里可以理解成为在python中,对象的赋值都是对象引用(内存地址)的传递。
  3. 由于a和b都指向同一个对象,所以对a的改动也会体现在b上,反之也是这样。

这里需要注意的是,因为字符串,元组等等是不可变的类型,所以修改的时候会替换旧的对象,产生一个新的地址。(在例子中是’139697967318608’变成了’139697936776808’)


浅拷贝

一段代码的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
>>> a = ['great', 4, ['go','ok','C#']]
>>> a
['great', 4, ['go', 'ok', 'C#']]
>>> import copy
>>> c = copy.copy(a)
>>> c
['great', 4, ['go', 'ok', 'C#']]
>>> id(a)
140327519625480
>>> [id(x) for x in a]
[140327519614376, 10914464, 140327519625416]
>>> id(c)
140327519627912
>>> [id(x) for x in c]
[140327519614376, 10914464, 140327519625416]
>>> a[0] = 'prada'
>>> a
['prada', 4, ['go', 'ok', 'C#']]
>>> c
['great', 4, ['go', 'ok', 'C#']]
>>> id(a)
140327519625480
>>> id(c)
140327519627912
>>> [id(x) for x in a]
[140327519616896, 10914464, 140327519625416]
>>> [id(x) for x in c]
[140327519614376, 10914464, 140327519625416]
>>> a[1] = 9
>>> a
['prada', 9, ['go', 'ok', 'C#']]
>>> c
['great', 4, ['go', 'ok', 'C#']]
>>> a[2].append('c')
>>> a
['prada', 9, ['go', 'ok', 'C#', 'c']]
>>> c
['great', 4, ['go', 'ok', 'C#', 'c']]
>>> id(a[2])
140327519625416
>>> id(c[2])
140327519625416

分析代码:

  1. 首先使用一个变量a,指向一个list类型的对象。
  2. 然后导入copy模块,使用模块中的浅拷贝函数copy(),对a指向的对象进行浅拷贝,然后拷贝生成新的对象赋值给c变量。
  3. 浅拷贝会常见一个新的对象,这个例子中c并不是a
  4. 但是对于对象中的元素,浅拷贝只能使用原始的元素的引用(内存地址),也就是说‘c[i]就是a[i]’
  5. 当对a进行修改的时候,因为a[0]是字符串类型,是不可变的类型,所以a[0]被修改以后会生成一个新的对象,也就是从原来的’140327519614376’变成现在的’140327519616896’
  6. 但是a[2]是一个列表,是可变的数据类型,对a[2]修改的时候,并不会生成新的对象,也就是不会占用新的内存地址,(140327519625416一直没变)所以对于a[2]的改变同样会在c上面体现。

深拷贝

还是先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import copy
>>> a
['prada', 9, ['go', 'ok', 'C#', 'c']]
>>> d = copy.deepcopy(a)
>>> d
['prada', 9, ['go', 'ok', 'C#', 'c']]
>>> id(a)
140327519625480
>>> id(d)
140327488829832
>>> [id(x) for x in a]
[140327519616896, 10914624, 140327519625416]
>>> [id(x) for x in d]
[140327519616896, 10914624, 140327488917704]
>>> a[0] = 'Chanel'
>>> a
['Chanel', 9, ['go', 'ok', 'C#', 'c']]
>>> d
['prada', 9, ['go', 'ok', 'C#', 'c']]
>>> [id(x) for x in a]
[140327519616952, 10914624, 140327519625416]
>>> a[2].append('py')
>>> a
['Chanel', 9, ['go', 'ok', 'C#', 'c', 'py']]
>>> d
['prada', 9, ['go', 'ok', 'C#', 'c']]
>>> id(a[2])
140327519625416
>>> id(d[2])
140327488917704
>>> a.append({'a':1,'b':2})
>>> a
['Chanel', 9, ['go', 'ok', 'C#', 'c', 'py'], {'b': 2, 'a': 1}]
>>> b
['Chanel', 9, ['go', 'ok', 'C#', 'c', 'py'], {'b': 2, 'a': 1}]
>>> c
['great', 4, ['go', 'ok', 'C#', 'c', 'py']]
>>> d
['prada', 9, ['go', 'ok', 'C#', 'c']]
>>> id(a)
140327519625480
>>> id(b)
140327519625480
>>> id(c)
140327519627912
>>> id(d)
140327488829832

分析代码:

  1. 首先还是一个变量a,指向list对象。
  2. 然后使用copy模块中的深拷贝函数deepcopy()进行深拷贝,将拷贝生成的新对象赋值给d
  3. 深拷贝会创建一个新的对象,这一点和浅拷贝是类似的。
  4. 但是和浅拷贝不同的是,深拷贝对于对象中的元素,深拷贝都会重新生成一份,而不是简单的使用原始元素的引用(内存地址),在上面的代码中,a[2]指向的是‘140327519625416’,但是d[2]指向的是’140327488917704’
  5. 在修改a里面的元素的时候,由于a[0]是不可变的字符串类型,那么a[0]修改会生成一个新的对象,所以修改a[0]并不会影响d,而改变a[2]的时候,因为是对a深拷贝之后赋值给d,所以a[2]和d[2]指向的是不同的对象,所以修改a[2]并不会影响d[2]

一些特殊的例子:

对于非容器类型,比如数字,字符串和其他原子类型的对象,没有拷贝这一说,也就是’obj is copy.copy(obj)’,’obj is copy.deepcopy(obj)’.

如果元组对象只包含了原子类型的对象,不能进行深拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
>>> a = ('1','2','3')
>>> import copy
>>> b = copy.deepcopy(a)
>>> a is b
True
>>> a
('1', '2', '3')
>>> b
('1', '2', '3')
>>> id(a)
140192635951360
>>> id(b)
140192635951360
>>> c = ('a','b','c',[1,2,3])
>>> c
('a', 'b', 'c', [1, 2, 3])
>>> d = copy.deepcopy(c)
>>> d
('a', 'b', 'c', [1, 2, 3])
>>> id(c)
140192605401240
>>> id(d)
140192605165432
>>> c is d
False