for循环执行时,获取可迭代对象,会一次性地产生一个迭代器,如果这个迭代器本身是可变的,比如列表,那么使用修改元素的方法,remove()和insert()等等,就会对它产生不可预知的影响,会影响迭代器的迭代。
lst=[1,2,4,3,5]
for i in lst:
if i % 2 ==0:
lst.remove(i)
print(lst)
#输出:
#[1, 4, 3, 5]
正如上面这段代码,对for循环生成的迭代器,遍历时我们可以想象是有一个指针不断地往下移动,这里的第一个元素是1,它模2不等于0,继续往下执行,移到第2个元素,第2个元素等于0,因此在列表就把它删除了,删掉后后面的元素会全部往前挪,那么这个时候内存的元素变成了[1, 4, 3, 5],刚刚指针已经移动到2元素这个位置,下一步应该移动向第3个元素,第三个元素其实已经是3了,4这个元素就被跳过了。
lst=[1,2,4,3,5]
for i in lst[:]: # lst[:]是列表浅拷贝,lst[:]与lst是两个不同的对象。
if i % 2 ==0:
lst.remove(i)
print(lst)
#输出:
#[1, 3, 5]
主要关系到是否会修改原始可变可迭代对象和原始可变可迭代对象中的部分对象。
x=[1,2,3]
z=x[:] # 浅拷贝
z[0]=8
print(id(x),x)
print(id(z),z)
# 输出:
# 1865121889792 [1, 2, 3]
# 1865121619776 [8, 2, 3]
x=[1,2,[3,4]]
y=x[:] # 浅拷贝
y[0],y[2][0]=9,9
print(id(x),x)
print(id(y),y)
# 输出:
# 1865122658880 [1, 2, [9, 4]]
# 1865121574784 [9, 2, [9, 4]]
在举例2中,我们发现x的一级元素没有改变,而二级元素改变了,也就是说浅拷贝让一级元素有了自己独立的空间,而二级元素仍然指向了 被拷贝对象(x) 的二级元素的内存区域。
可以理解为,浅拷贝只复制了父对象,就是只复制一级元素而不复制内部子对象,要想同时复制内部子对象与父对象,用copy模块中的深拷贝deepcopy()函数。
用copy模块中的深拷贝deepcopy()函数。
import copy
x=[1,2,[3,4]]
z=copy.deepcopy(x)
z[0],z[2][0]=9,9
print(id(x),x)
print(id(z),z)
# 输出:
# 1865121619776 [1, 2, [3, 4]]
# 1865121907200 [9, 2, [9, 4]]
下面主要介绍赋值、浅拷贝、深拷贝对可变对象及不可变对象的应用情况
a = 'hello'
可变对象与不可变对象,所指向的对象是内存中的值是否可以被改变。
注: id()函数用于获取对象的内存地址。
当改变不可变对象时,由于其值不能被改变,相当于把都原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
当改变可变对象时,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。
x=[1,2,3]
y=x #赋值
y[0]=4
print(id(x),x)
print(id(y),y)
# 输出:
# 1865121861120 [4, 2, 3]
# 1865121861120 [4, 2, 3]
我们发现x列表也跟着变化了。因为y和x都是对同一块内存空间的引用,两者的id地址是一致的,所以修改了y,x也会跟着修改。
赋值 只是复制了新对象的引用,不会开辟新的内存空间。
浅拷贝三种方式
lst = [1,2,[3,4]]
lst1=lst[:]
lst1=[item for item in lst]
lst = [1,2,[3,4]]
lst1=copy.copy(lst)
lst = [1,2,[3,4]]
lst1=list(lst)
浅拷贝 之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。
情况一
当浅拷贝的值是不可变对象(字符串、元组、数值类型)时,对象id值(id()函数用于获取对象的内存地址)与浅复制原来的值相同。
import copy
a=(1,2,3)
b=copy.copy(a)
print(a,id(a))
print(b,id(b))
# 输出:
# (1, 2, 3) 1865116784576
# (1, 2, 3) 1865116784576
a=(1,2,3)
b=copy.deepcopy(a) #深拷贝函数,下面讲深拷贝会讲到。
print(a,id(a))
print(b,id(b))
# 输出:
# (1, 2, 3) 1865116784576
#(1, 2, 3) 1865116784576
结论:
不可变对象类型,没有被拷贝说法,即使是用深拷贝,查看id的话也是一样的,如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。
所以不可变类型,不管是深拷贝还是浅拷贝,地址值和拷贝后的值都是一样的。
情况二
import copy
a=[1,2,3]
b=copy.copy(a)
print(a,id(a))
print(b,id(b))
# 输出:
# [1, 2, 3] 1865122658688
# [1, 2, 3] 1865121574592
a[0]=8
print(a,id(a))
print(b,id(b))
# 输出:
# [8, 2, 3] 1865122658688
# [1, 2, 3] 1865121574592
结论:
针对复制的对象中无复杂子对象,
原来值的id值与浅拷贝值的id不同。
原来值的发生改变,并不会影响浅拷贝的值,同时浅拷贝的值改变,也并不会影响原来的值。
对可变对象(有子对象)
import copy
a=[1,2,[1,2,3,4]]
b=copy.copy(a)
a[0]=8
print(a,id(a))
print(b,id(b))
# 输出:
# [8, 2, [1, 2, 3, 4]] 1865121846528
# [1, 2, [1, 2, 3, 4]] 1865121586368
import copy
a=[1,2,[1,2,3,4]]
b=copy.copy(a)
a[0],a[2][0]=9,9
print(a,id(a))
print(b,id(b))
# 输出:
# [9, 2, [9, 2, 3, 4]] 1865121688192
# [1, 2, [9, 2, 3, 4]] 1865121836672
结论:
针对复制的对象中有复杂子对象,
外层添加元素时,浅拷贝不会随原列表变化而变化;内层添加元素时,浅拷贝才会变化。
和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。
所以改变原有被复制对象不会对已经复制出来的新对象产生影响。
深拷贝方式
情况一
结论: 不可变对象类型,没有被拷贝说法,针对不可变对象的情况已经在浅拷贝情况中介绍,可以参见上面浅拷贝情况一的内容。这里就不重复讨论。
情况二
import copy
a=[1,2,3]
b=copy.deepcopy(a)
a[0]=8
print(a,id(a))
print(b,id(b))
# 输出:
# [8, 2, 3] 1865122658432
# [1, 2, 3] 1865121847488
对可变对象(有子对象)
import copy
a=[1,2,[1,2,3,4]]
b=copy.deepcopy(a)
a[0]=8
print(a,id(a))
print(b,id(b))
# 输出:
# [8, 2, [1, 2, 3, 4]] 1865122632640
# [1, 2, [1, 2, 3, 4]] 1865122658880
import copy
a=[1,2,[1,2,3,4]]
b=copy.deepcopy(a)
a[0],a[2][0]=9,9
print(a,id(a))
print(b,id(b))
# 输出:
# [9, 2, [9, 2, 3, 4]] 1865121574592
# [1, 2, [1, 2, 3, 4]] 1865122657600
结论:
原来值的id值与深拷贝值的id不同。
无论原列表如何变化,深拷贝都保持不变。
参考文章:
https://zhuanlan.zhihu.com/p/54011712