Python 赋值、浅拷贝与深拷贝
原创
©著作权归作者所有:来自51CTO博客作者wx6347c4235109b的原创作品,请联系作者获取转载授权,否则将追究法律责任
在分析这三个概念之前,我们先弄明白python中数据的可变类型与不可变类型
可变类型:列表和字典
不可变类型:数字、元组、字符串
在可变类型中,我们可以修改变量的赋值而不改变数据的内存地址,而在不可变类型中,我们想要修改变量的赋值,必须重新赋值,
如以下demo
字符串:
>>> str = "1234"
>>> str_01 = "1234"
>>> print(id(str))
139949746571520
>>> print(id(str_01))
139949746571520
>>> str_02 = str[:2]
>>> print(str_02)
12
>>> print(str)
1234
>>> print(id(str_02))
139949746537976
>>> print(id(str))
139949746571520
>>> str = "12"
>>> print(id(str))
139949746539296
在上面的demo中,str与str_01指向同一个对象,所以他们内存地址相同,而我们对str切片得到一个新的对象后,str_02指向的对象是一个新的地址,我们在修改str的数值后,str的内存地址也发生了变化,与我们上述结论并不冲突。
下面,我们来分析赋值、浅拷贝与深拷贝
一、赋值
通过以下demo我们来讨论
>>> str_01 = "hello world"
>>> str_02 = "hell0 world"
>>> str_03 = str_01
>>> print(id(str_01))
139949746571904
>>> print(id(str_02))
139949746571952
>>> print(id(str_03))
139949746571904
>>> list_01 = ["11","22","33"]
>>> list_02 = list_01
>>> print(list_01)
['11', '22', '33']
>>> print(list_02)
['11', '22', '33']
>>> print(id(list_01))
139949746456480
>>> print(id(list_02))
139949746456480
>>> list_01.append("44")
>>> print(list_01)
['11', '22', '33', '44']
>>> print(list_02)
['11', '22', '33', '44']
>>> print(id(list_01))
139949746456480
>>> print(id(list_02))
139949746456480
通过demo我们发现赋值操作(包括对象作为参数、返回值)不会开辟新的内存空间,它只是复制了对象的引用。也就是
说除了变量这个名字之外,没有其他的内存开销。如果数据是可变类型,修改了变量1,也就影响了变量2,同理,修改了变量2,也就影响了变量1。
二、浅拷贝
demo示例:
>>> str = "hello"
>>> list_01 = ["11","22"]
>>> list_03 = [str,list_01]
>>> list_04 = copy.copy(list_03)
>>> print(list_04)
['hello', ['11', '22']]
>>> print(list_03)
['hello', ['11', '22']]
>>> print(id(list_03))
139761828842384
>>> print(id(list_04))
139761828507728
>>> list_01.append("33")
>>> print(list_04)
['hello', ['11', '22', '33']]
>>> print(list_03)
['hello', ['11', '22', '33']]
>>> print(id(list_04))
139761828507728
>>> print(id(list_03))
139761828842384
>>> list_02 = list_01[:2]
>>> print(list_02)
['11', '22']
>>> print(id(list_01[0]))
139761828925232
>>> print(id(list_02[0]))
139761828925232
>>> print(id(list_02))
139761828920064
>>> print(id(list_01))
139761828841376
通过以上demo我们发现,浅拷贝会创建新对象,其内容非原对象本身的引用,而是原对象内第一层对象的引用。 浅拷贝有三种形式:切片操作、工厂函数、copy模块中的copy函数。 比如上述的列表list_01,我们对list_01进行切片操作:list_02= list_01[:2] 可以发现list_01与list_02不再是同一个对象,他们的内存地址不一样,但他们里面的元素指向同一个地址, copy也是一样的现象。同样的现象还有工厂函数,类似于list() 。
结论:
浅拷贝产生的对象b不再是对象a了,使用is判断可以发现他们不是同一个对象,使用id查看,他们也不指向同一片内存空间。但是当我们使用id函数来查看a和b中元素的地址时,可以看到二者包含的元素的地址是相同的。 在这种情况下,列表a和b是不同的对象,修改列表b理论上不会影响到列表a。 但是要注意的是,浅拷贝之所以称之为浅拷贝,是它仅仅只拷贝了一层,在列表a中有一个嵌套的list,如果我们修改了它,情况就不一样了。 比如:a[3].append('java')。查看列表b,会发现列表b也发生了变化,这是因为,我们修改了嵌套的list,修改外层元素,会修改它的引用,让它们指向别的位置,修改嵌套列表中的元素,列表的地址并未发生变化,指向的都是用一个位置。
深拷贝
demo如下
>>> list_01 = ["11","22"]
>>> list_02 = ["33",list_01]
>>> list_03 = copy.deepcopy(list_02)
>>> print(id(list_01))
140616962848672
>>> print(id(list_02))
140616962849680
>>> print(id(list_03))
140616962927576
>>> print(id(list_02[1]))
140616962848672
>>> print(id(list_03[1]))
140616963002312
>>> list_01.append("44")
>>> print(list_02)
['33', ['11', '22', '44']]
>>> print(list_03)
['33', ['11', '22']]
通过上面的demo我们发现,通过深拷贝即使嵌套的列表具有更深的层次,也不会产生任何影响,因为深拷贝拷贝出来的对象根本就是一个全新的对象,不再与原来的对象有任何的关联。 我们无论对原有对象怎么修改,也不会影响到我们通过新拷贝产生的新对象。
本人疑问:
>>> str_01 = "1234"
>>> str_02 = "1234"
>>> str_03 = "hello"
>>> str_04 = "hello"
>>> print(id(str_01))
140616962995232
>>> print(id(str_02))
140616962995232
>>> print(id(str_03))
140616962995904
>>> print(id(str_04))
140616962995904
>>> str_05 = "hello python"
>>> str_06 = "hello python"
>>> print(id(str_05))
140616962976096
>>> print(id(str_06))
140616962973800
为什么都是对变量赋同一个值,str_05与str_06的内存地址不一样,本人猜测可能是字符串的长度超过一定长度时,对变量赋同样的值也会产生新的对象,不过不知道python这样的机制是为了什么