目录
1. 可变对象和不可变对象
在 Python 中,数据类型可以分为两大类:可变对象和不可变对象。
常见的可变对象有:
- 列表(list)
- 字典(dict)
- 集合(set)
常见的不可变对象有:
-
整数(int)
-
浮点数(float)
-
字符串(str)
-
元组(tuple)
-
布尔值(bool)
可变对象的值可以在对象创建后被修改,即可以在不改变内存地址的情况下修改对象的内容。
例如:
>>> a = [0,1]
>>> id_old = id(a) # 修改前a的地址
>>> a.append(1)
>>> id_new = id(a) # 修改后a的地址
>>> print(id_old == id_new)
True
a = [0,1]:创建了一个列表对象,id_old是这个列表对象的地址。
a.append(2):我们想要在a的末尾添加2。由于列表是可变对象,因此可以直接修改。id_new为修改后a的地址,比较后发现,id_new=id_old,说明修改前后的内存地址没有发生改变。也就是说,Python直接对a进行了修改。
不可变对象不允许修改对象的内容。如果需要更改内容,必须创建一个新的对象。
例如:
>>> b = 1
>>> id_old = id(b) # 修改前b的地址
>>> b = 2
>>> id_new = id(b) # 修改后b的地址
>>> print(id_old == id_new)
False
b=1:创建了一个整数对象,id_old是这个整数对象的地址。
b=2:我们想要把b修改为2。但是由于整数是不可变对象,因此必须再创建一个新的整数对象,并令其名称为b,值为2,is_new是这个整数对象的地址。由于id_old和id_new是两个整数对象的地址,所以id_old不等于id_new。
在接下来的内容中,我将以整数对象为例,讲解不可变对象的性质;以列表对象为例,讲解可变对象的性质
让我们画一个图,以便于更加清晰表示上面的例子:
对于列表:Python可以直接对列表进行修改,因此,修改前后,内存中只有一个列表。
对于整数:Python不能修改整数的值。如果要修改整数的值,必须创建一个新的整数对象,因此,在修改前后,内存中有两个整数。一个是修改前的整数1,一个是新创建的整数2。
2. 用=
赋值的问题
当我们使用=
来直接赋值时,Python 不会创建一个新的对象,而只会创建一个新的引用。
例如,
>>> c = [1,2]
>>> id_c = id(c)
>>> d = c
>>> id_d = id(d)
>>> print(id_c == id_d)
True
当我们执行d=c时,Python不会为变量d创建一个新的列表,而是让变量c和d共享同一个列表,c和d只不过是对同一个列表的两个引用。
如下图
因此,当我们改变变量c中的值时,会发现,变量d中的值也被改变了。
>>> c.append(3)
>>> print(d)
[1,2,3]
原因是因为,c和d共同指向的列表的值发生了变化,如下图
因此,d的值也改变了。
需要注意的是,对于不可变对象,如果有如下的代码
>>> x = 1 >>> y = x >>> x = 2 >>> print(y) 1
你会发现,y的值并没有随着x的改变而改变。这是因为,整数对象是不可变对象。
在执行y=x后,x、y和整数1的关系如下图,此时,x,y是同一个整数的两个引用
执行x=2后,由于整数是不可变对象,因此Python会创建一个新的值为2的整数,并将这个整数赋给x,如下图
可以看到,y还是指向原来的整数,并没有受到影响,因此,y的值不会改变
3. copy模块登场
为了解决上面的问题,copy模块闪亮登场。
copy模块中,只有两个函数,copy
和deepcopy
看下面的例子
>>> x = [1,2]
>>> y = copy.copy(x)
>>> z = copy.deepcopy(x)
>>> x.append(3)
>>> print(y)
>>> print(z)
[1,2]
[1,2]
可以看到,在使用了copy
和deepcopy
之后,在对x进行修改时,y和z的值就不会跟着发生变化了。
那么,copy
和deepcopy
有什么区别呢?
为此,我们需要重新认识一下列表对象,以及了解一下浅拷贝和深拷贝的概念
4. 重新认识列表对象
考虑下面这两个列表,其中x是一维的列表,y是二维的列表
>>> x = [1,2,3]
>>> y = [[1,2,3],[4,5,6],[7,8,9]]
其中x所对应的一维列表,在内存中是这样的:
x是列表对象的引用。在列表对象中,存着三个地址:分别是整数1的地址,整数2的地址以及整数3的地址。
至于y所对应的二维列表,在内存中是这样的:
y是二维列表的引用。在二维列表中,存着三个一维列表的地址。在一维列表中,分别存着其中整数元素的地址。
5. 浅拷贝,深拷贝
知道列表对象在内存中是什么样子的,就可以引出浅拷贝和深拷贝了。
浅拷贝(copy.copy())
假设我们令
>>> y = [[1,2,3],[4,5,6],[7,8,9]]
>>> z = copy.copy(y)
那么,在内存中,执行的操作如下:
Python复制了一个二维的列表,并将这个二维列表赋给了z。
但需要注意的是,旧的二维列表中的地址,和新的二维列表中的地址是相同的。也就是说,新旧两个二维列表指向相同的一维列表。
为了方便,这里省略了整数对象
这就是浅拷贝的含义,浅拷贝只复制“最上层”的列表。
如果我们执行下面的代码
>>> y = [[1,2,3],[4,5,6],[7,8,9]]
>>> z = copy.copy(y)
>>> z.append([1,2,3])
>>> print(y)
>>> z[0] = [1,2]
>>> print(y)
[[1,2,3],[4,5,6],[7,8,9]]
[[1,2],[4,5,6],[7,8,9]]
可以看到,当向z中添加元素时,y的值不会变化。而修改z的第一个列表时,y的值也会发生变化。
当向z中添加元素时, 内存中的操作如下
可以看到,此时,在z对应的二维列表中,新添加了一个地址4。地址4指向的就是z中新添加的元素。
不过,虽然z中多了一个地址4,但是y中的地址没有发生变化,因此,不会影响到y对应的二维列表。
当修改z的第一个列表时,内存中的操作如下
红色的列表表示被修改的列表。
可以看到,y和z中,都有指向红色列表的地址。因此一旦红色列表被修改,那么y和z的值都会改变。
一维列表的浅拷贝
考虑下面的代码
>>> x = [1,2,3]
>>> z = copy.copy(x)
内存中的操作如下
同样的,Python复制了一个新的一维列表,并将这个新一维列表赋给了z。
同时,旧一维列表中的地址,和新一维列表中的地址是相同的。也就是说,新旧两个一维列表指向相同的整数。
当我们执行下面的代码时:
>>> z[0] = 0
由于整数是不可变对象,而不可变对象不允许修改对象的内容,如果需要更改内容,必须创建一个新的对象。
因此我们执行z[0]=0时,实际上创建了一个新的整数对象,并令新的整数对象的值为0
这时候,x所指的整数没有发生变化,因此x的值不会变。
深拷贝(copy.deepcopy())
假设我们令
>>> y = [[1,2,3],[4,5,6],[7,8,9]]
>>> z = copy.deepcopy(y)
那么此时,内存中执行的操作如下
此时,Python不再只是复制最上层的列表,而是将y中所有的元素都完完整整的复制了一遍,并赋给了z。因此,这时候,无论对z进行任何操作,都无法影响y的值了。
浅拷贝,深拷贝,直接赋值的区别
>>> y = [[1,2,3],[4,5,6],[7,8,9]]
>>> z1 = y # 直接赋值
>>> z2 = copy.copy(y) # 浅拷贝
>>> z3 = copy.deepcopy(y) # 深拷贝
>>>
>>> z1.append([1,2,3]) # 会改变y的值
>>> z1[0] = [1,2] # 会改变y的值
>>>
>>> z2.append([1,2,3]) # 不会改变y的值
>>> z2[0] = [1,2] # 会改变y的值
>>>
>>> z3.append([1,2,3]) # 不会改变y的值
>>> z3[0] = [1,2] # 不会改变y的值
总结一下吧
标签:Python,整数,列表,对象,拷贝,copy,id From: https://www.cnblogs.com/rh-li/p/18489103