【python基础】—可变可迭代对象修改的问题及浅拷贝深拷贝的问题

文章目录

  • 引入:可变可迭代对象修改的问题
    • 1.直接修改可变可迭代对象的问题
    • 2.赋值、浅拷贝和深拷贝三种方式对可变可迭代对象修改问题
  • 一、理解概念
  • 二、赋值
  • 三、浅拷贝
  • 四、深拷贝


引入:可变可迭代对象修改的问题

1.直接修改可变可迭代对象的问题

  • 问题描述:

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]

2.赋值、浅拷贝和深拷贝三种方式对可变可迭代对象修改问题

  • 问题描述

主要关系到是否会修改原始可变可迭代对象原始可变可迭代对象中的部分对象

  • 举例1
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]
  • 举例2
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'

【python基础】—可变可迭代对象修改的问题及浅拷贝深拷贝的问题_第1张图片

  • 类型:属于对象,而非变量。
  • 不可变对象:一旦创建就不可以修改的对象,包括字符串、元组、数值类型。
  • 可变对象:可以修改的对象,包括列表、字典、集合。

可变对象与不可变对象,所指向的对象是内存中的值是否可以被改变。

注: id()函数用于获取对象的内存地址。

当改变不可变对象时,由于其值不能被改变,相当于把都原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址
当改变可变对象时,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。

二、赋值

  • 举例1
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]
  • copy函数
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

结论:
针对复制的对象中有复杂子对象,
外层添加元素时,浅拷贝不会随原列表变化而变化;内层添加元素时,浅拷贝才会变化。

四、深拷贝

和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。
所以改变原有被复制对象不会对已经复制出来的新对象产生影响。

深拷贝方式

  • deepcopy函数

情况一

  • 不可变对象

结论: 不可变对象类型,没有被拷贝说法,针对不可变对象的情况已经在浅拷贝情况中介绍,可以参见上面浅拷贝情况一的内容。这里就不重复讨论。

情况二

  • 可变对象(无子对象)
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

你可能感兴趣的:(python基础,python,java,开发语言)