master
/ 6.2.5 列表赋值与复制.ipynb

6.2.5 列表赋值与复制.ipynb @masterview markup · raw · history · blame

Notebook

6.7 列表的赋值和复制

将一个列表ls直接赋值给另一个变量lsnew时,并不会产生新的对象,只相当于给原列表存储的位置多加了一个标签lsnew,可以同时使用ls和lsnew两个标签访问原列表。当列表ls的值发生变化时,lsnew同时发生变化。

当使用copy()方法复制或用列表切片再赋值时,相当于创建一个新对象,再拷贝数据的一个副本,称为浅复制。新对象与原列表无直接关联,对其中一个操作也不会影响另一个对象。

In [1]:
# 只拷贝第一层的叫浅拷贝,如元素为可变数据类型,只拷贝其首地址,可变数据类型元素值发生变化,会影响用浅拷贝创建的对象。
# 递归拷贝到底的叫深拷贝,拷贝结果完全独立于原对象
import copy

ls = [2,5,['a',[22,33],'c'],9] # 创建一个对象ls
ls1 = ls                       # ls对象加一个新标签,值随ls变化而变化
ls2 = ls[1:3]                  # 切片,得到新对象
ls3 = ls.copy()                # 产生新对象
ls4 = [x for x in ls]          # 列表推导式

ls5 = copy.deepcopy(ls)        # 产生新对象,递归拷贝ls中所有元素,对象创建后完全独立于ls
ls.append(10)                  # ls 末尾增加一个元素
ls[2][1].append(44)            # ls 中序号为2的元素['a',[22,33],'c']中序号为1的元素[22,33]末尾增加一个元素
print(id(ls),id(ls1),id(ls2),id(ls3),id(ls4),id(ls5))

print("ls:",ls)
print("ls1:",ls1)
print("ls2:",ls2)
print("ls3:",ls3)
print("ls4:",ls4)
print("ls5:",ls5)
140053318249440 140053318249440 140053318249680 140053318248800 140053318250000 140053318250240
ls: [2, 5, ['a', [22, 33, 44], 'c'], 9, 10]
ls1: [2, 5, ['a', [22, 33, 44], 'c'], 9, 10]
ls2: [5, ['a', [22, 33, 44], 'c']]
ls3: [2, 5, ['a', [22, 33, 44], 'c'], 9]
ls4: [2, 5, ['a', [22, 33, 44], 'c'], 9]
ls5: [2, 5, ['a', [22, 33], 'c'], 9]

image.png

由上例可以看出,ls1和ls的id值相同,表示它们指向同一序列,而ls2、ls3、ls4和ls5的id值各不相同,表明它们指向不同对象。 不同的是,ls5在创建时,递归复制了ls里所有元素,称为深复制,这种方法创建的对象完全独立于原始对象,原始对象的任何变化都不会影响到深复制创建的对象。 ls2、ls3和ls4只复制一层元素,当其中某可变数据类型元素的值发生变化时,ls2、ls3和ls4中对应元素的值也会发生变化。 与字符串一样,当列表乘一个整数n时是一种重复操作,相当于一个id的对象被复制n次。

In [ ]:
ls = [[ ]] * 3   # ls[0]被复制3次
print(ls)        # [[], [], []]
print(id(ls[0]),id(ls[1]),id(ls[2])) 
# ls[0],ls[1],ls[2]id相同,是同一对象的不同标签
# 输出2438949169160 2438949169160 2438949169160
ls[0].append(3)   # 列表ls[0]新增一个元素
print(ls)     # [[3], [3], [3]],对象值改变,通过不同标签访问的都是修改过的值
ls[0].append(5)   # 列表ls[0]新增一个元素
print(ls)
# [[3, 5], [3, 5], [3, 5]],对象值改变,通过不同标签访问的都是修改过的值

严格来说,Python 中赋值语句并不是把对象的值赋给变量名,而是在变量名和对象之间创建绑定关系,或者说给存储在数据起一个名字或加一个标签以方便重复访问。 对于自身可变(如列表)或者包含可变项(如列表做为元素)的集合对象,在程序开发时有时会需要生成其副本用于改变操作,避免改变原对象。copy 模块提供了通用的浅层 (shallow) 复制和深层 (deep)复制操作,语法如下:

In [ ]:
copy.copy(x)             # 返回 x 的浅层复制。
copy.deepcopy(x[, memo]) # 返回 x 的深层复制。
exception copy.error     # 针对模块特定错误引发。

浅层复制和深层复制都会创新一个新的对象,他们之间的区别仅与复合对象 (即包含其他对象的对象,如列表或类的实例) 相关: ● 一个 浅层复制 会构造一个新的复合对象,然后(在可能的范围内)将原对象中找到的 引用 插入其中。 ● 一个 深层复制 会构造一个新的复合对象,然后递归地将原始对象中所找到的对象的 副本 插入。

In [ ]:
import copy
ls = [1,2,'123',[4,5,(7,8,[9,10])]]
ls2 = copy.copy(ls)  
ls3 = copy.deepcopy(ls) 

深度复制操作通常存在两个问题, 而浅层复制操作并不存在这些问题: ● 递归对象 (直接或间接包含对自身引用的复合对象) 可能会导致递归循环。 ● 由于深层复制会复制所有内容,因此可能会过多复制(例如本应该在副本之间共享的数据)。 deepcopy() 函数通过以下操作避免这些问题: ● 保留在当前复制过程中已复制的对象的 "备忘录" (memo) 字典; ● 允许用户定义的类重载复制操作或复制的组件集合。 该模块不复制模块、方法、栈追踪(stack trace)、栈帧(stack frame)、文件、套接字、窗口、数组以及任何类似的类型。它通过不改变地返回原始对象来(浅层或深层地)“复制”函数和类;这与 pickle 模块处理这类问题的方式是相似的。 制作字典的浅层复制可以使用 dict.copy() 方法,而制作列表的浅层复制可以通过赋值整个列表的切片完成,例如:

In [ ]:
copied_list = original_list[:]

类可以使用与控制序列化(pickling)操作相同的接口来控制复制操作,关于这些方法的描述信息可参考 pickle模块。实际上,copy 模块使用的正是从 copyreg 模块中注册的 pickle 函数。 想要给一个类定义它自己的拷贝操作实现,可以通过定义特殊方法 copy() 和 deepcopy()。 调用前者以实现浅层拷贝操作,该方法不用传入额外参数。 调用后者以实现深层拷贝操作;它应传入一个参数即 memo字典。 如果 deepcopy() 实现需要创建一个组件的深层拷贝,它应当调用 deepcopy() 函数并以该组件作为第一个参数,而将 memo 字典作为第二个参数。

In [ ]:
# 以下两种方法都可以获得包含以3个空列表为元素的列表
lists = [[]] * 3
print(lists)  # [[], [], []]

ls = [[] for i in range(3)]
print(ls)    # [[], [], []]

从图中可以看到,lists的三个元素引用自同一个对象,是重复引用。而ls中的3个元素是分3次创建的3个空列表,是3个独立的对象。两个列表虽然看似相同,但包含的对象数量是不同的。

In [ ]:
# 以下两种方法都可以获得包含以3个空列表为元素的列表
lists = [[]] * 3
lists[0].append(3)
print(lists)  # [[3], [3], [3]]
# 上述代码中3个元素中由lists[0]重复得到,都引用自lists[0],是同一个对象。
# 当这个对象的改变时,引用这个对象的所有名字的值也会发生相应的变化

ls = [[] for i in range(3)]
ls[0].append(3)
ls[1].append(5)
ls[2].append(7)
print(ls)   # [[3], [5], [7]]

lists中的三个空列表是同一个对象的3次引用,所以改变其中一个值的时候,3个元素的值同时发生了变化。而ls中的三个空列表是3个独立的对象,改变列表的值的时候,相互之间无影响。

In [ ]:
# 以下两种方法都可以获得包含以3个空列表为元素的列表
lists = [[]] * 3
lists[0].append(3)
lists[1].append(6)
lists[2].append(9)
print(lists)  # [[3, 6, 9], [3, 6, 9], [3, 6, 9]]
# 上述代码中3个元素中由lists[0]重复得到,都引用自lists[0],是同一个对象。
# 当这个对象的改变时,引用这个对象的所有名字的值也会发生相应的变化

ls = [[] for i in range(3)]
ls[0].append(3)
ls[1].append(5)
ls[2].append(7)
print(ls)   # [[3], [5], [7]]

In [1]:
# 只拷贝第一层的叫浅拷贝,如元素为可变数据类型,只拷贝其首地址,可变数据类型元素值发生变化,会影响用浅拷贝创建的对象。
# 递归拷贝到底的叫深拷贝,拷贝结果完全独立于原对象
import copy

ls = [2,5,['a',[22,33],'c'],9] # 创建一个对象ls
ls1 = ls                       # ls对象加一个新标签,值随ls变化而变化

ls.append(10)                  # ls 末尾增加一个元素

print(id(ls),id(ls1))
print("ls:",ls)
print("ls1:",ls1)
140376180026256 140376180026256
ls: [2, 5, ['a', [22, 33], 'c'], 9, 10]
ls1: [2, 5, ['a', [22, 33], 'c'], 9, 10]
In [ ]: