Fork me on GitHub

python可变与不可变对象

Abstract:介绍python中的可变对象与不可变对象的概念。
此处输入图片的描述

定义

  • 不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
  • 可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。

例子

  • 不可变(immutable):int、字符串(string)、float、(数值型number)、元组(tuple)

  • 可变(mutable):字典型(dictionary)、列表型(list)、集合(set)

定义默认参数要牢记

默认参数必须指向不变对象

1
2
3
4
5
6
7
def add_end(L=[]):
L.append('END')
return L
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

1
2
3
4
5
6
7
8
9
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
>>> add_end()
['END']
>>> add_end()
['END']

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
>>> a = [1]
>>> id(a)
2278158310408
>>> a += [2]
>>> id(a)
2278158310408
>>> a = 4 #不可变
>>> id(a)
1944540032
>>> a += 3
>>> id(a)
1944540128

但是当不可变对象比较大时,这个规律不符合,并不会创建新的对象。常量池

python中的复制,浅拷贝和深拷贝

复制只传递对象的引用,也就是对象的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> a = [1,2,3]
>>> b = a
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> id(a)
1264995283336
>>> id(b)
1264995283336
>>> a.append(2)
>>> a
[1, 2, 3, 2]
>>> b
[1, 2, 3, 2]

浅拷贝指把存放变量的地址值传给被赋值,最后两个变量引用了同一份地址。copy会根据数据类型为可变还是不可变进行判断:如果是不可变类型,和复制相同;如果是可变类型,只是拷贝第一层(也就是对于list类型中,其中的元素中指向的其他地址不变化)。
用到的方法是 copy.copy()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>>import copy
>>> c=copy.copy(a)
>>> c
[1, 2, 3, 2]
>>> id(c)
1264997856200
>>> id(a)
1264995283336
>>> a
[1, 2, 3, 2]
>>> b=2

>>> c=copy.copy(a)
>>> id(a)
1264995283336
>>> id(c)
1264997856264

>>> d=copy.copy(b)
>>> id(b)
1354853888
>>> id(d)
1354853888

发现,同样是浅拷贝,拷贝后的a和b却给了不同的处理。因为a是list,是可变对象,而b是int数据类型,属于不可变对象
换言之,对于可变对象,浅拷贝与复制不同,拷贝的结果存放在一个新的空间。而对于不可变对象,拷贝和复制的意义相同,都指向同一个地址空间。

1
2
3
4
5
6
7
8
>>> id(c)
1264997856200
>>> id(a)
1264995283336
>>> id(b)
1354853888
>>> id(d)
1354853888

但是,对于不可变对象,当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。因此,改变一个变量,其地址会发生改变,而原来复制出的变量则指向原来的值。

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
>>> a = 2
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
>>> id(a)
1354853888
>>> id(b)
1354853888
>>> id(c)
1354853888
>>> id(d)
1354853888
>>> a = a + 3
>>> a
5
>>> b
2
>>> c
2
>>> d
2
>>> id(a)
1354853984
>>> id(b)
1354853888
>>> id(c)
1354853888
>>> id(d)
1354853888

可见,对于不可变对象,深拷贝,浅拷贝,复制 效果一样

深拷贝包含对象里面的内容的拷贝,重新开辟一个新的空间,所以原始对象的改变不会造成深拷贝里任何子元素的改变。
实际上,一般所谓的拷贝操作,都是在list上进行的,所以只需要知道在lists上着三种拷贝操作的意义和结论即可
深拷贝和浅拷贝的区别直接看代码:

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
>>> a = [[1,1,1],[2,2,2]]
>>> b = copy.copy(a)
>>> c = copy.deepcopy(a)

>>> id(a)
1264997889288
>>> id(b)
1264997862472
>>> id(c)
1264997889736
#深拷贝和浅拷贝的区别在,深拷贝复制了整个对象,而浅拷贝只是复制了第一层的元素
>>> id(a[0])
1264997875208
>>> id(b[0])
1264997875208
>>> id(c[0])
1264997861640

>>> a.append(2)
>>> b
[[1, 1, 1], [2, 2, 2]]
>>> c
[[1, 1, 1], [2, 2, 2]]
>>> a
[[1, 1, 1], [2, 2, 2], 2]

>>> a[0].append(2)
>>> a
[[1, 1, 1, 2], [2, 2, 2], 2]
>>> b
[[1, 1, 1, 2], [2, 2, 2]]
>>> c
[[1, 1, 1], [2, 2, 2]]

-------------本文结束感谢阅读-------------