10.深浅拷贝 五 、深浅拷贝
1.赋值
赋值,只是创建一个变量,该变量指向原来内存地址
2.浅拷贝
浅拷贝,在内存中只额外创建第一层数据,
3.深拷贝
深拷贝,在内存中将所有的数据重新创建一份(排除最后一层,即:python内部对字符串和数字的优化)
引用 VS 拷贝
引用:
1、赋值操作总是存储对象的引用,而不是这些对象的拷贝
2、因为赋值操作会产生相同对象的多个引用,需要意识到在原处修改可变对象时可能会影响程序中其他地方对相同对象的其他引用
拷贝:
1、没有限制条件的分片表达式L[:]能够复制序列
2、字典copy方法X.copy()能够复制字典
3、copy标准库模块能够生成完整拷贝
拷贝需要注意的是:
1、无条件值的分片以及字典的copy方法只能做顶层复制,即不能够复制嵌套的数据结构
2、如果要做深层嵌套的数据结构的完整的、独立的拷贝,就要使用copy模块的deepcopy方法对象任意嵌套对象做完整的复制
数字和字符串
对于 数字 和 字符串 而言,因为数字和字符串内部的优化机制,赋值、浅拷贝和深拷贝无意义,因为其永远指向同一个内存地址。
数字:
字符串:
赋值:
#数字
>>> n1 = 123 >>> type(n1) <type 'int'> >>> n2 = n1 >>> n3 = n1 >>> id(n1) 44726624 >>> id(n2) 44726624 >>> id(n3) 44726624 >>>
#字符串赋值
>>> n1= "abc"
>>> n2 = n1
>>> n3 = n1
>>> id(n1)
51623672
>>> id(n2)
51623672
>>> id(n3)
51623672
>>>
浅拷贝:
#数字
>>> import copy >>> n1 = 123 >>> n2= copy.copy(n1) >>> n3 = copy.copy(n1) >>> id(n1) 44726624 >>> id(n2) 44726624 >>> id(n3) 44726624 >>>
#字符串
>>> import copy
>>> n1= "abc"
>>> n2 = copy.copy(n1)
>>> n3 = copy.copy(n1)
>>> id(n1)
51623672
>>> id(n2)
51623672
>>> id(n3)
51623672
>>>
深拷贝:
#字符串
>>> n1 = "abc" >>> n2 = copy.deepcopy(n1) >>> n3 = copy.deepcopy(n1) >>> id(n1) 51623672 >>> id(n2) 51623672 >>> id(n3) 51623672 >>>
结论:
你可以看到3个变量指向的是同一个地址,对于 数字 和 字符串 而言,赋值、浅拷贝和深拷贝无意义,因为其永远指向同一个内存地址
其他基本数据类型
对于字典、元组和列表 而言,进行赋值、浅拷贝和深拷贝时,其内存地址的变化是不同的。
字典、元组和列表的赋值
赋值:
#对于字典、元组和列表 而言,进行赋值、浅拷贝和深拷贝时,其内存地址的变化是不同的。
#赋值,只是创建一个变量,该变量指向原来内存地址,如:
>>> n1 = {"k1":"hello","k2":123,"k3":["tom",23]} >>> n2 = n1 >>> id(n1) 53020976 >>> id(n2) 53020976
>>> id(n1['k3'])
53008704
>>> id(n2['k3'])
53008704
>>>
字典、元组和列表的浅拷贝
浅拷贝
>>> import copy
>>> n1 = {"k1":"hello","k2":123,"k3":["tom",23]}
>>> n3 = copy.copy(n1)
>>> id(n1)
52614896
>>> id(n3)
52615760
#由此可见第一层是变化
>>> id(n1['k3'])
53026984
>>> id(n3['k3'])
53026984
即复制只改了第一层数据,其他各层是相同的,可通过id函数查看变化
字典、元组和列表的深拷贝
深拷贝
#深拷贝,在内存中将所有的数据重新创建一份(排除最后一层,即:python内部对字符串和数字的优化)
>>> import copy >>> n1 = {"k1":"hello","k2":123,"k3":["tom",23]} >>> n4 = copy.deepcopy(n1) >>> id(n1) 52617056 >>> id(n4) 53020400 >>> id(n1['k1']) 52941984 >>> id(n4['k1']) 52941984 >>> id(n1['k3']) 53010880 >>> id(n4['k3']) 53010224 >>> id(n1['k3'][0]) 52983928 >>> id(n4['k3'][0]) 52983928 >>>
深浅拷贝都是对源对象的复制,占用不同的内存空间
如果源对象只有一级目录的话,源对象做任何改动,不影响深浅拷贝对象
如果源对象不止一级目录的话,源对象做任何改动,都要影响浅拷贝,但不影响深拷贝
序列对象的切片其实是浅拷贝,即只拷贝顶级的对象
深浅拷贝的应用场景
比如在CMDB系统中,我们定义了一个报警模版call给所有的服务器使用,此时有一批特殊应用的服务器需要不通的报警参数,我们既不想单独新建模版来一个一个添加报警参数,又不想修改默认模版而影响其他机器的报警阈值。此时我们就需要用深拷贝来完成。示例如下:
默认模版:
call = {
'cpu':80,
'mem':80,
'disk':80
}
此时的特殊模版需求是cpu报警阀值要改成75,而不影响默认模版使用
代码如下:
#!/usr/bin/env python
#coding:utf-8
import copy
#默认模版
call = {
'cpu':[80,],
'mem':[80,],
'disk':[80,]
}
#新模板
new_call = copy.deepcopy(call)
#修改新模版
new_call['cpu'][0] = 75
#查看新旧模版的值
print('新的模版为:%s' %(new_call))
print('默认模版为:%s' %(call))
#打印结果:
新的模版为:{'mem': 80, 'disk': 80, 'cpu': 75}
默认模版为:{'mem': 80, 'disk': 80, 'cpu': 80}
#上面的代码显示我们只改了新的模版,而默认模版并没有修改,并且我们用了copy而不是单独新建模版。
假设我们用浅拷贝来做结果是这样的:
#默认模版
call = {
'cpu':[80,],
'mem':[80,],
'disk':[80,]
}
#新模板
new_call = copy.copy(call)
#修改新模版
new_call['cpu'][0] = 75
#查看新旧模版的值
print('新的模版为:%s' %(new_call))
print('默认模版为:%s' %(call))
#打印的结果:
新的模版为:{'mem': [80], 'disk': [80], 'cpu': [75]}
默认模版为:{'mem': [80], 'disk': [80], 'cpu': [75]}
#默认模版和新模版都被修改了,显然这不是我们要的结果
分析原因:深拷贝的时候python将字典的所有数据在内存中新建了一份,所以如果你修改新的模版的时候老模版不会变。相反,在浅copy 的时候,python仅仅将最外层的内容在内存中新建了一份出来,字典第二层的列表并没有在内存中新建,所以你修改了新模版,默认模版也被修改了。
注:深copy并没有在内存中新建列表中的元素,因为上例中列表的元素是数字,这得益于python对数字和字符串的优化机制,即,若字符串或者数字没有修改,它的内存位置永远不变,直到它被修改。
再看一个例子:
浅拷贝:
>>> dict = {"a":("apple",),"bo":{"b":"banna","o":"orange"},"g":["grape","grapefruit"]} >>> dict2 = dict.copy()
>>> id(dict),id(dict2)
(50951032L, 56199368L)
>>> id(dict['g']),id(dict2['g'])
(56169992L, 56169992L)
>>> id(dict["g"][0]),id(dict2["g"][0])
(56211456L, 56211456L)
#原有的 dict["g"][0]的值为grape >>> dict["g"][0] = "game" #第一次我修改的是第二层的数据 >>> dict {'a': ('apple',), 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['game', 'grapefruit']} >>> dict2 {'a': ('apple',), 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['game', 'grapefruit']}
#修改后这一层id没变
>>> id(dict),id(dict2)
(50951032L, 56199368L)
#修改后这一层id没变
>>> id(dict['g']),id(dict2['g'])
(56169992L, 56169992L)
>>> id(dict["g"][0]),id(dict2["g"][0]) #从这里可以看出第二层他们是用的内存地址
(56197072L, 56197072L)
>>> dict["a"] = "tom" #注意第二次这里修改的是第一层
>>> dict
{'a': 'tom', 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['game', 'grapefruit']}
>>> dict2
{'a': ('apple',), 'bo': {'b': 'banna', 'o': 'orange'}, 'g': ['game', 'grapefruit']}
>>> id(dict)
50951032L
>>> id(dict2)
56199368L
>>> id(dict["a"]),id(dict2["a"])