Python_赋值和深浅copy
Python中赋值和深浅copy存储变化
在刚学python的过程中,新手会对python中赋值、深浅copy的结果感到丈二和尚。经过多次在网上查找这几个过程的解释说明以及实现操作观察,初步对这几个过程有了浅显的认识。以下内容仅是我在学习过程中遇到的问题,然后上网搜验证,最后理解。博文也许没有将这部分内容写明白,也许有不对的地方,如果有大佬看到希望能指点一下新人。随着后面的学习以及理解会再次补充此内容。
- id函数
id方法的返回值就是对象的内存地址
- 执行赋值语句
在python中执行一条赋值语句,python解释器做了这两件事情:
1.在内存中创建一个字符串;
2.在内存中创建一个变量,并把字符串的内存地址赋值给变量;
1 >>> a='haizei' 2 >>> id(a) 3 1895098520440 #字符串‘haizei’的内存地址
python解释器在内存中创建一个字符串‘haizei’,创建一个指针a,并把字符串的内存地址保存在指针中。指针就指向了字符串,访问指针a就是访问字符串‘haizei’。
-
赋值a赋值给变量b
1 >>> a='haizei' 2 >>> id(a) 3 1895098520440 4 >>> b=a 5 >>> id(b) 6 1895098520440 #a和b的指向的值的内存地址是相同的,说明赋值是把变量a指向的值的内存地址赋值给b,使b和a一样也指向同一个内存地址。b和a都是存储的值的内存地址
执行b=a,创建了一个变量b,并把b指向a指向的字符串的内存地址。
执行b=a,创建一个指针b,并把存储在指针a中的内存地址赋值给指针b,这样指针b就也指向了a指向的字符串。赋值完成后b就和a没有任何关系了,只和它指向的字符串有关系。
- 重新给a赋值
1 >>> a='haizei' 2 >>> id(a) 3 1895098520440 4 >>> b=a 5 >>> id(b) 6 1895098520440 7 >>> a='lufei' #python解释器在内存中新开辟一块空间创建字符串‘lufei’,并把a重新指向字符串‘abc’ 8 >>> id(a) 9 1895098520496 10 >>> id(b) 11 1895098520440 #变量b指向的还是原来的字符串
前面给变量a赋值,把a的值赋给b,结果是在内存中创建了一个值,a和b都指向了这个值。现在重新给a赋一个新的值,执行一条赋值语句,过程是在内存中创建一个新的值,然后把新值的内存地址保存在变量指针a中。结果是内存中存在两个值b指向原来的值,a指向新创建的值。以上结果说明对于赋值操作,如果是将另一个变量的值赋给一个变量实际上是将另一个变量存储的值的内存地址同样保存在被赋予值的变量中;如果是给变量赋一个新的值,那么相当于是一个初始化操作会在内存中开辟一段空间创建一个值,将值的内存地址保存在被赋值的变量指针中。
- 创建复杂数据结构
1 >>> name=['lufei','suolong','namei'] 2 >>> id(name) 3 1895098506504 4 >>> id(name[0]) 5 1895098520496 6 >>> id(name[1]) 7 1895098520608 8 >>> id(name[2]) 9 1895098520664
创建的列表的名称为name,从代码结果可以看到:列表name指向的整体内存地址和列表中每个元素指向的地址都不相同。说明对于复杂的数据结构来说,里面存储的也只是一个个值的内存地址。从下图可以看出来对于元组、列表、字典这样的复杂的数据结构,在内存中其变量和值是分开的,每个元素都对应一个变量指针,变量指针指向值的位置。变量和值的存储是分开的,把整个数据结构赋值给另一个变量的执行过程是在内存中创建相同数量的一些变量,然后把原数据结构中存储在变量中的值的地址赋值给新创建的变量。结果如右图。
对列表中的元素的增删改查操作不会影响到列表本身的内存地址,改变的是列表内的某个元素的地址引用;如果是重新初始化一个列表那么修改的是列表名称指向的整个列表的内存地址。可当我们给name这个列表重新初始化赋值时,就给name这个变量重新赋予了一个新的内存地址,覆盖了原来的列表的地址。
1 >>> name=['lufei','suolong','namei'] 2 >>> id(name) 3 1895098506504 4 >>> id(name[0]) 5 1895098520496 6 >>> id(name[1]) 7 1895098520608 8 >>> id(name[2]) 9 1895098520664 10 >>> name[0]='zhang' #重新给序号为0的元素赋值 11 >>> id(name) #整个列表的内存地址没有改变 12 1895098506504 13 >>> id(name[0]) 14 1895098520552 #序号为0的元素的内存地址发生改变 15 >>> name=['zhao','qian','sun'] #重新初始化赋值name这个变量 16 >>> id(name) 17 1895098512136 #name这个变量指向的内存地址发生了改变
这里重新给name变量赋值,赋一个新的列表给name。整个过程以及结果和上面的单个变量的赋值是类似的,不同的是这里是一个列表是多个单个元素(变量)的集合。相当于是对多个变量做这样的操作。
- 浅copy
1 >>> name={'lufei':'cap','haizei':['suolong','namei','qiaoba']} #1、创建字典name 2 >>> a=name.copy() #执行copy方法,吧结果赋值给字典a 3 >>> id(name) 4 1895098004488 5 >>> id(a) 6 1895098007368 #从内存地址上看原来的name的内存地址和a的内存地址不相同,说明copy是创建了一个name的副本,把副本的内存地址保存在变量a中 7 >>> id(name['lufei']) 8 1895098521056 9 >>> id(a['lufei']) 10 1895098521056 #分别查看原字典name和副本字典a中键‘lufei’的值的内存地址,地址相同。说明这里存储的是同一个值 11 >>> id(name['haizei']) 12 1895098506504 13 >>> id(a['haizei']) 14 1895098506504 #同样副本中另一个键的值的内存地址也是相同的,说明两个字典内的键都指向相同的值 15 >>> name['lufei']='haha' #执行赋值语句修改name字典中键‘lufei’的值 16 >>> name['haizei'].remove('namei') #调用字典name中‘haizei’键的值,并调用值的remove方法移除列表中的‘namei’元素 17 >>> name 18 {'haizei': ['suolong', 'qiaoba'], 'lufei': 'haha'} 19 >>> a 20 {'haizei': ['suolong', 'qiaoba'], 'lufei': 'cap'} #分别查看字典name和a,发现对字典name执行赋值语句时,副本没有跟着修改,对键‘haizei’做的修改副本跟着做了修改? 21 >>> id(name) 22 1895098004488 23 >>> id(a) 24 1895098007368 #分别查看两个字典的内存地址,和修改之前的内存地址是一样的。说明修改复杂的数据结构中元素的值不影响变量对整体数据结构内存地址的指向(数据结构的内存地址没有改变) 25 >>> id(name['lufei']) 26 1895098520944 27 >>> id(a['lufei']) 28 1895098521056 #分别查看两个字典中键‘lufei’的值的内存地址,发现name中该键的值被修改了其内存地址改变了。而字典a的键的值没有改变,对应的该键的值的内存地址没有改变。 29 >>> id(name['haizei']) 30 1895098506504 31 >>> id(a['haizei']) 32 1895098506504 #对字典name中键‘haizei’的值做了修改,但是查看该键的值的内存地址时,两个字典都没有变化,和修改前是一样的?
在理解深浅copy的时候,先用一张图来表示字典在内存中的存在方式,再说明深浅copy分别copy的是字典的那些部分。当对原字典或者副本进行修改时,修改的部分在 内存中造成什么变化,是否对原字典和副本字典产生了影响。当理解了这些的时候也就理解了深浅copy
这是我们在内存中创建的字典的存在形式。可以这么理解,字典name保存整体字典的内存地址从而指向这个字典,字典内包含两个键,键1保存的是一个值的内存地址从而指向这个值,键2保存的是一个列表的整体地址从而指向这个列表。列表内又有三个指针分别保存三个值的内存地址作为列表的三个元素。
浅copy复制的是哪部分?浅copy仅复制了图中的字典变量的部分。也就是复制了键1下保存的内存地址和键而的内存地址
当修原字典或者副本字典的键1关联的值时,在内存中开辟一片新的空间存储一个新的值,并把新创建的值的内存地址保存到被修改的字典的对应键的指针里。因此被修改的字典的键指向了新创建的值,未被修改的字典中相同的键指向的还是原来的值,所以当修改的键对应的值是一个简单的数据类型的值时,修改后原字典和副本字典的值出现了不同。
当修改的键对应的值是一个数据结构时,由于原字典中键2中仅保存了列表的整体地址,副本字典中copy的时候,键2也仅仅是copy的整个列表的内存地址。原字典和副本字典都不清楚列表中值的内存地址是多少,只是通过列表的整体地址可以访问到列表中的值。在这种情况下列表中的值发生变化时就对原字典和副本字典都产生了影响。
- 深copy
1 >>> from copy import deepcopy #使用深copy时需要从copy模块中导入deepcopy 2 >>> name={'captain':'lufei','sailor':['suolong','namei','qiaoba']} 3 >>> copy_name=deepcopy(name) #执行深copy 4 >>> id(name) 5 1595431826440 6 >>> id(copy_name) 7 1595435745864 #查看原字典和副本字典的内存地址,地址不同 8 >>> id(name['sailor']) 9 1595437385096 10 >>> id(copy_name['sailor']) 11 1595437383816 #查看原字典和副本字典列表值对应的键下存储的内存地址,地址不同。说明当键对应的值是一个复杂的数据结构时deepcopy会继续复制到下一层。在浅copy中原字典和副本字典的这 12 >>> id(name['sailor'][1]) #里是相同的。 13 1595435816248 14 >>> id(copy_name['sailor'][1]) 15 1595435816248 #当查看原字典和副本字典中列表值下的元素的地址时发现,其内存地址又是相同的。
由上面的内存地址的查看可以绘制出深copy时copy的范围对应的图
可以对比浅copy时的结果的图中不一样的地方来理解。
高级语言中,变量是对内存及其地址的抽象。在python中变量的内存地址和变量值的内存地址是分开的--python中内存的变量存储情况。
每个变量中都存储了这个变量的地址,而不是值本身;对于复杂的数据结构来说,里面存储的也只是每个元素的内存地址而已,并不是值本身;
在python中执行一条赋值语句如a='abc'python解释器做了两件事情:
1.在内存中创建了一个字符串‘abc’
2.在内存中创建了一个名为a的变量,并把它指向‘abc’
变量的每次初始化,都开辟了一个新的空间,并把新内容的内存地址赋值给变量